Compare commits

...

53 Commits

Author SHA1 Message Date
Georgi Matev
880cb899b5
Update README.md (#5466)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic
words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-10-11 11:42:38 -07:00
Georgi Matev
779bb70301
Update README.md type (#5465)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic
words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-10-11 11:38:56 -07:00
Georgi Matev
2487072d95
Update README.md with archival notice (#5463)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [x] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic
words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-10-11 11:33:20 -07:00
dependabot[bot]
ad927afbc1
⬆️ Bump sass from 1.78.0 to 1.79.1 in /website (#5444)
Bumps [sass](https://github.com/sass/dart-sass) from 1.78.0 to 1.79.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.79.1</h2>
<p>To install Sass 1.79.1, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>No user-visible changes.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1791">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.79.1</h2>
<ul>
<li>No user-visible changes.</li>
</ul>
<h2>1.79.0</h2>
<ul>
<li>
<p><strong>Breaking change</strong>: Passing a number with unit <code>%</code> to the <code>$alpha</code> parameter
of <code>color.change()</code>, <code>color.adjust()</code>, <code>change-color()</code>, and <code>adjust-color()</code>
is now interpreted as a percentage, instead of ignoring the unit. For example,
<code>color.change(red, $alpha: 50%)</code> now returns <code>rgb(255 0 0 / 0.5)</code>.</p>
</li>
<li>
<p><strong>Potentially breaking compatibility fix</strong>: Sass no longer rounds RGB channels
to the nearest integer. This means that, for example, <code>rgb(0 0 1) != rgb(0 0 0.6)</code>. This matches the latest version of the CSS spec and browser behavior.</p>
</li>
<li>
<p><strong>Potentially breaking compatibility fix</strong>: Passing large positive or negative
values to <code>color.adjust()</code> can now cause a color's channels to go outside that
color's gamut. In most cases this will currently be clipped by the browser and
end up showing the same color as before, but once browsers implement gamut
mapping it may produce a different result.</p>
</li>
<li>
<p>Add support for CSS Color Level 4 [color spaces]. Each color value now tracks
its color space along with the values of each channel in that color space.
There are two general principles to keep in mind when dealing with new color
spaces:</p>
<ol>
<li>
<p>With the exception of legacy color spaces (<code>rgb</code>, <code>hsl</code>, and <code>hwb</code>), colors
will always be emitted in the color space they were defined in unless
they're explicitly converted.</p>
</li>
<li>
<p>The <code>color.to-space()</code> function is the only way to convert a color to
another color space. Some built-in functions may do operations in a
different color space, but they'll always convert back to the original space
afterwards.</p>
</li>
</ol>
</li>
<li>
<p><code>rgb</code> colors can now have non-integer channels and channels outside the normal
gamut of 0-255. These colors are always emitted using the <code>rgb()</code> syntax so
that modern browsers that are being displayed on wide-gamut devices can
display the most accurate color possible.</p>
</li>
<li>
<p>Add support for all the new color syntax defined in Color Level 4, including:</p>
<ul>
<li><code>oklab()</code>, <code>oklch()</code>, <code>lab()</code>, and <code>lch()</code> functions;</li>
<li>a top-level <code>hwb()</code> function that matches the space-separated CSS syntax;</li>
<li>and a <code>color()</code> function that supports the <code>srgb</code>, <code>srgb-linear</code>,
<code>display-p3</code>, <code>a98-rgb</code>, <code>prophoto-rgb</code>, <code>rec2020</code>, <code>xyz</code>, <code>xyz-d50</code>, and
<code>xyz-d65</code> color spaces.</li>
</ul>
</li>
<li>
<p>Add new functions for working with color spaces:</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="5fa04d3dbc"><code>5fa04d3</code></a> Fix sass-parser publishing (<a href="https://redirect.github.com/sass/dart-sass/issues/2349">#2349</a>)</li>
<li><a href="d740d02e10"><code>d740d02</code></a> Emit deprecation warnings for the legacy JS API (<a href="https://redirect.github.com/sass/dart-sass/issues/2343">#2343</a>)</li>
<li><a href="a957eeadd1"><code>a957eea</code></a> Bump chokidar to v4 (<a href="https://redirect.github.com/sass/dart-sass/issues/2347">#2347</a>)</li>
<li><a href="aa35aa20dd"><code>aa35aa2</code></a> Bump bufbuild/buf-setup-action in /.github/util/initialize (<a href="https://redirect.github.com/sass/dart-sass/issues/2346">#2346</a>)</li>
<li><a href="f826ed2e54"><code>f826ed2</code></a> Stop emitting <code>mixed-decls</code> in a bunch of unnecessary cases (<a href="https://redirect.github.com/sass/dart-sass/issues/2342">#2342</a>)</li>
<li><a href="2f0d0daaf4"><code>2f0d0da</code></a> Merge pull request <a href="https://redirect.github.com/sass/dart-sass/issues/2341">#2341</a> from sass/feature.color-4</li>
<li><a href="de181d9192"><code>de181d9</code></a> Poke CI</li>
<li><a href="34f98c703b"><code>34f98c7</code></a> Update color API tests</li>
<li><a href="422f037ebd"><code>422f037</code></a> Fix a typo</li>
<li><a href="4db68a1d4f"><code>4db68a1</code></a> Merge pull request <a href="https://redirect.github.com/sass/dart-sass/issues/2339">#2339</a> from sass/merge-main</li>
<li>Additional commits viewable in <a href="https://github.com/sass/dart-sass/compare/1.78.0...1.79.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.78.0&new-version=1.79.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-09-18 05:58:05 +00:00
ashmrtn
b086f8c3ff
Use new client created for PnP ops in purge script (#5442)
PowerShell switched to requiring certificate credentials so the existing cleanup jobs have been failing since the switch

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [x] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-09-17 23:08:14 +00:00
dependabot[bot]
d9bf48be7e
⬆️ Bump dompurify from 3.0.6 to 3.1.6 in /website (#5437)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.0.6 to 3.1.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/cure53/DOMPurify/releases">dompurify's releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.1.6</h2>
<ul>
<li>Fixed an issue with the execution logic of attribute hooks to prevent bypasses, thanks <a href="https://github.com/kevin-mizu"><code>@​kevin-mizu</code></a></li>
<li>Fixed an issue with element removal leading to uncaught errors through DOM Clobbering, thanks <a href="https://github.com/realansgar"><code>@​realansgar</code></a></li>
<li>Fixed a minor problem with the bower file pointing to the wrong dist path</li>
<li>Fixed several minor typos in docs, comments and comment blocks, thanks <a href="https://github.com/Rotzbua"><code>@​Rotzbua</code></a></li>
<li>Updated several development dependencies</li>
</ul>
<h2>DOMPurify 3.1.5</h2>
<ul>
<li>Fixed a minor issue with the dist paths in <code>bower.js</code>, thanks <a href="https://github.com/HakumenNC"><code>@​HakumenNC</code></a></li>
<li>Fixed a minor issue with sanitizing HTML coming from copy&amp;paste Word content, thanks <a href="https://github.com/kakao-bishop-cho"><code>@​kakao-bishop-cho</code></a></li>
</ul>
<h2>DOMPurify 3.1.4</h2>
<ul>
<li>Fixed an issue with the recently implemented <code>isNaN</code> checks, thanks <a href="https://github.com/tulach"><code>@​tulach</code></a></li>
<li>Added several new popover attributes to allow-list, thanks <a href="https://github.com/Gigabyte5671"><code>@​Gigabyte5671</code></a></li>
<li>Fixed the tests and adjusted the test runner to cover all branches</li>
</ul>
<h2>DOMPurify 3.1.3</h2>
<ul>
<li>Fixed several mXSS variations found by and thanks to <a href="https://github.com/kevin-mizu"><code>@​kevin-mizu</code></a> &amp; <a href="https://github.com/Ry0taK"><code>@​Ry0taK</code></a></li>
<li>Added better configurability for comment scrubbing default behavior</li>
<li>Added better hardening against Prototype Pollution attacks, thanks <a href="https://github.com/kevin-mizu"><code>@​kevin-mizu</code></a></li>
<li>Added better handling and readability of the <code>nodeType</code> property, thanks <a href="https://github.com/ssi02014"><code>@​ssi02014</code></a></li>
<li>Fixed some smaller issues in README and other documentation</li>
</ul>
<h2>DOMPurify 3.1.2</h2>
<ul>
<li>Addressed and fixed a mXSS variation found by <a href="https://github.com/kevin-mizu"><code>@​kevin-mizu</code></a></li>
<li>Addressed and fixed a mXSS variation found by <a href="https://twitter.com/hash_kitten">Adam Kues</a> of Assetnote</li>
<li>Updated tests for older Safari and Chrome versions</li>
</ul>
<h2>DOMPurify 3.1.1</h2>
<ul>
<li>Fixed an mXSS sanitiser bypass reported by <a href="https://github.com/icesfont"><code>@​icesfont</code></a></li>
<li>Added new code to track element nesting depth</li>
<li>Added new code to enforce a maximum nesting depth of 255</li>
<li>Added coverage tests and necessary clobbering protections</li>
</ul>
<p><strong>Note that this is a security release and should be upgraded to immediately. Please also note that further releases may follow as the underlying vulnerability is apparently new and further variations may be discovered.</strong></p>
<h2>DOMPurify 3.1.0</h2>
<ul>
<li>Added new setting <code>SAFE_FOR_XML</code> to enable better control over comment scrubbing</li>
<li>Updated README to warn about <em>happy-dom</em> not being safe for use with DOMPurify yet</li>
<li>Updated the LICENSE file to show the accurate year number</li>
<li>Updated several build and test dependencies</li>
</ul>
<h2>DOMPurify 3.0.11</h2>
<ul>
<li>Fixed another conditional bypass caused by Processing Instructions, thanks <a href="https://github.com/Ry0taK"><code>@​Ry0taK</code></a></li>
<li>Fixed the regex for HTML Custom Element detection, thanks <a href="https://github.com/AlekseySolovey3T"><code>@​AlekseySolovey3T</code></a></li>
</ul>
<h2>DOMPurify 3.0.10</h2>
<ul>
<li>Fixed two possible bypasses when sanitizing an XML document and later using it in HTML, thanks <a href="https://github.com/Slonser"><code>@​Slonser</code></a></li>
<li>Bumped up some build and test dependencies</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="4083a9096b"><code>4083a90</code></a> Merge pull request <a href="https://redirect.github.com/cure53/DOMPurify/issues/978">#978</a> from cure53/main</li>
<li><a href="90a10a14af"><code>90a10a1</code></a> fix: Fixed a typo on the README</li>
<li><a href="65df0428f0"><code>65df042</code></a> chore: Preparing 3.1.6 release</li>
<li><a href="6e03334bab"><code>6e03334</code></a> fix: Made sure that remove() is not called directly from node</li>
<li><a href="00fc06cf57"><code>00fc06c</code></a> fix: Fixed a DOM clobbering issue leading to an error being thrown</li>
<li><a href="f8c2ef5911"><code>f8c2ef5</code></a> Merge pull request <a href="https://redirect.github.com/cure53/DOMPurify/issues/977">#977</a> from cure53/dependabot/npm_and_yarn/multi-99ca4f73d8</li>
<li><a href="e5112ec40a"><code>e5112ec</code></a> build(deps): bump ws and socket.io-adapter</li>
<li><a href="9978cecea2"><code>9978cec</code></a> docs: Added better security warning about SAFE_FOR_XML to README</li>
<li><a href="fa542df7e8"><code>fa542df</code></a> fix: Changed the order for attribute checks slightly for safer hooks</li>
<li><a href="b8b552cb21"><code>b8b552c</code></a> Merge pull request <a href="https://redirect.github.com/cure53/DOMPurify/issues/975">#975</a> from cure53/dependabot/npm_and_yarn/multi-2d3aef8690</li>
<li>Additional commits viewable in <a href="https://github.com/cure53/DOMPurify/compare/3.0.6...3.1.6">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.0.6&new-version=3.1.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/alcionai/corso/network/alerts).

</details>
2024-09-16 20:47:23 +00:00
dependabot[bot]
fe261b22c5
⬆️ Bump sass from 1.77.0 to 1.78.0 in /website (#5423)
Bumps [sass](https://github.com/sass/dart-sass) from 1.77.0 to 1.78.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.78.0</h2>
<p>To install Sass 1.78.0, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>
<p>The <code>meta.feature-exists</code> function is now deprecated. This deprecation is named <code>feature-exists</code>.</p>
</li>
<li>
<p>Fix a crash when using <code>@at-root</code> without any queries or children in the indented syntax.</p>
</li>
</ul>
<h3>JS API</h3>
<ul>
<li>
<p>Backport the deprecation options (<code>fatalDeprecations</code>, <code>futureDeprecations</code>, and <code>silenceDeprecations</code>) to the legacy JS API. The legacy JS API is itself deprecated, and you should move off of it if possible, but this will allow users of bundlers and other tools that are still using the legacy API to still control deprecation warnings.</p>
</li>
<li>
<p>Fix a bug where accessing <code>SourceSpan.url</code> would crash when a relative URL was passed to the Sass API.</p>
</li>
</ul>
<h3>Embedded Sass</h3>
<ul>
<li>
<p>Explicitly expose a <code>sass</code> executable from the <code>sass-embedded</code> npm package. This was intended to be included in 1.63.0, but due to the way platform-specific dependency executables are installed it did not work as intended. Now users can run <code>npx sass</code> for local installs or just <code>sass</code> when <code>sass-embedded</code> is installed globally.</p>
</li>
<li>
<p>Add linux-riscv64, linux-musl-riscv64, and android-riscv64 support for the <code>sass-embedded</code> npm package.</p>
</li>
<li>
<p>Fix an edge case where the Dart VM could hang when shutting down when requests were in flight.</p>
</li>
<li>
<p>Fix a race condition where the embedded host could fail to shut down if it was closed around the same time a new compilation was started.</p>
</li>
<li>
<p>Fix a bug where parse-time deprecation warnings could not be controlled by the deprecation options in some circumstances.</p>
</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1780">full changelog</a> for changes in earlier releases.</p>
<h2>Dart Sass 1.77.8</h2>
<p>To install Sass 1.77.8, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>No user-visible changes.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1778">full changelog</a> for changes in earlier releases.</p>
<h2>Dart Sass 1.77.5</h2>
<p>To install Sass 1.77.5, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.78.0</h2>
<ul>
<li>
<p>The <code>meta.feature-exists</code> function is now deprecated. This deprecation is
named <code>feature-exists</code>.</p>
</li>
<li>
<p>Fix a crash when using <code>@at-root</code> without any queries or children in the
indented syntax.</p>
</li>
</ul>
<h3>JS API</h3>
<ul>
<li>
<p>Backport the deprecation options (<code>fatalDeprecations</code>, <code>futureDeprecations</code>,
and <code>silenceDeprecations</code>) to the legacy JS API. The legacy JS API is itself
deprecated, and you should move off of it if possible, but this will allow
users of bundlers and other tools that are still using the legacy API to
still control deprecation warnings.</p>
</li>
<li>
<p>Fix a bug where accessing <code>SourceSpan.url</code> would crash when a relative URL was
passed to the Sass API.</p>
</li>
</ul>
<h3>Embedded Sass</h3>
<ul>
<li>
<p>Explicitly expose a <code>sass</code> executable from the <code>sass-embedded</code> npm package.
This was intended to be included in 1.63.0, but due to the way
platform-specific dependency executables are installed it did not work as
intended. Now users can run <code>npx sass</code> for local installs or just <code>sass</code> when
<code>sass-embedded</code> is installed globally.</p>
</li>
<li>
<p>Add linux-riscv64, linux-musl-riscv64, and android-riscv64 support for the
<code>sass-embedded</code> npm package.</p>
</li>
<li>
<p>Fix an edge case where the Dart VM could hang when shutting down when requests
were in flight.</p>
</li>
<li>
<p>Fix a race condition where the embedded host could fail to shut down if it was
closed around the same time a new compilation was started.</p>
</li>
<li>
<p>Fix a bug where parse-time deprecation warnings could not be controlled by
the deprecation options in some circumstances.</p>
</li>
</ul>
<h2>1.77.8</h2>
<ul>
<li>No user-visible changes.</li>
</ul>
<h2>1.77.7</h2>
<ul>
<li>
<p>Declarations that appear after nested rules are deprecated, because the
semantics Sass has historically used are different from the semantics
specified by CSS. In the future, Sass will adopt the standard CSS semantics.</p>
<p>See <a href="https://sass-lang.com/d/mixed-decls">the Sass website</a> for details.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="90a70ef168"><code>90a70ef</code></a> Fix failing double check test for sass-parser (<a href="https://redirect.github.com/sass/dart-sass/issues/2330">#2330</a>)</li>
<li><a href="b1d5f987d9"><code>b1d5f98</code></a> Backport deprecation API to legacy JS API (<a href="https://redirect.github.com/sass/dart-sass/issues/2293">#2293</a>)</li>
<li><a href="56a42371e0"><code>56a4237</code></a> Delete unreachable <code>default</code> clause. (<a href="https://redirect.github.com/sass/dart-sass/issues/2323">#2323</a>)</li>
<li><a href="a7f623dd13"><code>a7f623d</code></a> Bump bufbuild/buf-setup-action in /.github/util/initialize (<a href="https://redirect.github.com/sass/dart-sass/issues/2319">#2319</a>)</li>
<li><a href="9f82850504"><code>9f82850</code></a> Ignore new <code>unreachable_switch_default</code> warning. (<a href="https://redirect.github.com/sass/dart-sass/issues/2318">#2318</a>)</li>
<li><a href="798cd7cf57"><code>798cd7c</code></a> Update pubspec.yaml (<a href="https://redirect.github.com/sass/dart-sass/issues/2321">#2321</a>)</li>
<li><a href="2bf3ae0eed"><code>2bf3ae0</code></a> Fix a comment (<a href="https://redirect.github.com/sass/dart-sass/issues/2316">#2316</a>)</li>
<li><a href="eb6c19e53c"><code>eb6c19e</code></a> Initial implementation of a PostCSS-compatible parser JS API (<a href="https://redirect.github.com/sass/dart-sass/issues/2304">#2304</a>)</li>
<li><a href="c3cccefe2e"><code>c3cccef</code></a> Bump dartdoc from 8.0.7 to 8.0.8 (<a href="https://redirect.github.com/sass/dart-sass/issues/2300">#2300</a>)</li>
<li><a href="f0a01829ce"><code>f0a0182</code></a> docs: Fix link to custom importer (<a href="https://redirect.github.com/sass/dart-sass/issues/2315">#2315</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/sass/dart-sass/compare/1.77.0...1.78.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.77.0&new-version=1.78.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-09-04 05:54:58 +00:00
dependabot[bot]
18e3661289
⬆️ Bump webpack from 5.89.0 to 5.94.0 in /website (#5417)
Bumps [webpack](https://github.com/webpack/webpack) from 5.89.0 to 5.94.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/webpack/webpack/releases">webpack's releases</a>.</em></p>
<blockquote>
<h2>v5.94.0</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Added runtime condition for harmony reexport checked</li>
<li>Handle properly <code>data</code>/<code>http</code>/<code>https</code> protocols in source maps</li>
<li>Make <code>bigint</code> optimistic when browserslist not found</li>
<li>Move <code>@​types/eslint-scope</code> to dev deps</li>
<li>Related in asset stats is now always an array when no related found</li>
<li>Handle ASI for export declarations</li>
<li>Mangle destruction incorrect with export named default properly</li>
<li>Fixed unexpected asi generation with sequence expression</li>
<li>Fixed a lot of types</li>
</ul>
<h2>New Features</h2>
<ul>
<li>Added new external type &quot;module-import&quot;</li>
<li>Support <code>webpackIgnore</code> for <code>new URL()</code> construction</li>
<li>[CSS] <code>@import</code> pathinfo support</li>
</ul>
<h2>Security</h2>
<ul>
<li>Fixed DOM clobbering in auto public path</li>
</ul>
<h2>v5.93.0</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Generate correct relative path to runtime chunks</li>
<li>Makes <code>DefinePlugin</code> quieter under default log level</li>
<li>Fixed mangle destructuring default in namespace import</li>
<li>Fixed consumption of eager shared modules for module federation</li>
<li>Strip slash for pretty regexp</li>
<li>Calculate correct contenthash for CSS generator options</li>
</ul>
<h2>New Features</h2>
<ul>
<li>Added the <code>binary</code> generator option for asset modules to explicitly keep source maps produced by loaders</li>
<li>Added the <code>modern-module</code> library value for tree shakable output</li>
<li>Added the <code>overrideStrict</code> option to override strict or non-strict mode for javascript modules</li>
</ul>
<h2>v5.92.1</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Doesn't crash with an error when the css experiment is enabled and contenthash is used</li>
</ul>
<h2>v5.92.0</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Correct tidle range's comutation for module federation</li>
<li>Consider runtime for pure expression dependency update hash</li>
<li>Return value in the <code>subtractRuntime</code> function for runtime logic</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="eabf85d858"><code>eabf85d</code></a> chore(release): 5.94.0</li>
<li><a href="955e057abc"><code>955e057</code></a> security: fix DOM clobbering in auto public path</li>
<li><a href="9822387362"><code>9822387</code></a> test: fix</li>
<li><a href="cbb86ede32"><code>cbb86ed</code></a> test: fix</li>
<li><a href="5ac3d7f2cd"><code>5ac3d7f</code></a> fix: unexpected asi generation with sequence expression</li>
<li><a href="2411661bd1"><code>2411661</code></a> security: fix DOM clobbering in auto public path</li>
<li><a href="b8c03d4772"><code>b8c03d4</code></a> fix: unexpected asi generation with sequence expression</li>
<li><a href="f46a03ccbc"><code>f46a03c</code></a> revert: do not use heuristic fallback for &quot;module-import&quot;</li>
<li><a href="60f189871a"><code>60f1898</code></a> fix: do not use heuristic fallback for &quot;module-import&quot;</li>
<li><a href="66306aa456"><code>66306aa</code></a> Revert &quot;fix: module-import get fallback from externalsPresets&quot;</li>
<li>Additional commits viewable in <a href="https://github.com/webpack/webpack/compare/v5.89.0...v5.94.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpack&package-manager=npm_and_yarn&previous-version=5.89.0&new-version=5.94.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/alcionai/corso/network/alerts).

</details>
2024-08-30 22:06:35 +00:00
dependabot[bot]
d87e24d839
⬆️ Bump @docusaurus/plugin-google-gtag from 3.4.0 to 3.5.1 in /website (#5402)
Bumps [@docusaurus/plugin-google-gtag](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag) from 3.4.0 to 3.5.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/plugin-google-gtag</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.5.1 (2024-08-09)</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10384">#10384</a> fix(core): algolia context import (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10382">#10382</a> fix(theme-algolia): useDocusaurusContext import error (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 2</h4>
<ul>
<li>Sunghyun Cho (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
<li>Sébastien Lorber (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
<h2>3.5.0 (2024-08-09)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10375">#10375</a> feat(blog): add <code>onUntruncatedBlogPosts</code> blog options (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10376">#10376</a> feat(theme): show unlisted/draft banners in dev mode (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9252">#9252</a> feat(blog): add feed xlst options to render beautiful RSS and Atom feeds (<a href="https://github.com/Xebec19"><code>@​Xebec19</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10216">#10216</a> feat(blog): authors page (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10339">#10339</a> feat(translation): add Estonian default translation (<a href="https://github.com/chirbard"><code>@​chirbard</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10325">#10325</a> feat(translations): Indonesian translation (<a href="https://github.com/priyadi"><code>@​priyadi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10335">#10335</a> feat(mdx-loader): wrap mdx content title (<code># Title</code>) in <code>&lt;header&gt;</code> for concistency (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10222">#10222</a> feat(blog): author header social icons (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-google-analytics</code>, <code>docusaurus-plugin-google-gtag</code>, <code>docusaurus-plugin-google-tag-manager</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-plugin-vercel-analytics</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10286">#10286</a> feat(core): allow plugins to self-disable by returning null (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10252">#10252</a> feat(blog): group sidebar items by year (<code>themeConfig.blog.sidebar.groupByYear</code>) (<a href="https://github.com/alicelovescake"><code>@​alicelovescake</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10224">#10224</a> feat(blog): warn duplicate and inline authors (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils-validation</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10241">#10241</a> feat(mdx): support recma plugins (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10344">#10344</a> fix(translations): fix wrong Estonian (et) translations and typos (<a href="https://github.com/Gekd"><code>@​Gekd</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10360">#10360</a> fix(translations): Fix and Improve Spanish translations (<a href="https://github.com/sergioalmela"><code>@​sergioalmela</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10235">#10235</a> fix(theme-translation): add missing German (de) theme.admonition translations (<a href="https://github.com/franzd1"><code>@​franzd1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10342">#10342</a> fix(search): fix algolia search ignore ctrl + F in search input (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/plugin-google-gtag</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.5.1 (2024-08-09)</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10384">#10384</a> fix(core): algolia context import (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10382">#10382</a> fix(theme-algolia): useDocusaurusContext import error (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 2</h4>
<ul>
<li>Sunghyun Cho (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
<li>Sébastien Lorber (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
<h2>3.5.0 (2024-08-09)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10375">#10375</a> feat(blog): add <code>onUntruncatedBlogPosts</code> blog options (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10376">#10376</a> feat(theme): show unlisted/draft banners in dev mode (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9252">#9252</a> feat(blog): add feed xlst options to render beautiful RSS and Atom feeds (<a href="https://github.com/Xebec19"><code>@​Xebec19</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10216">#10216</a> feat(blog): authors page (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10339">#10339</a> feat(translation): add Estonian default translation (<a href="https://github.com/chirbard"><code>@​chirbard</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10325">#10325</a> feat(translations): Indonesian translation (<a href="https://github.com/priyadi"><code>@​priyadi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10335">#10335</a> feat(mdx-loader): wrap mdx content title (<code># Title</code>) in <code>&lt;header&gt;</code> for concistency (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10222">#10222</a> feat(blog): author header social icons (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-google-analytics</code>, <code>docusaurus-plugin-google-gtag</code>, <code>docusaurus-plugin-google-tag-manager</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-plugin-vercel-analytics</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10286">#10286</a> feat(core): allow plugins to self-disable by returning null (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10252">#10252</a> feat(blog): group sidebar items by year (<code>themeConfig.blog.sidebar.groupByYear</code>) (<a href="https://github.com/alicelovescake"><code>@​alicelovescake</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10224">#10224</a> feat(blog): warn duplicate and inline authors (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils-validation</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10241">#10241</a> feat(mdx): support recma plugins (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10344">#10344</a> fix(translations): fix wrong Estonian (et) translations and typos (<a href="https://github.com/Gekd"><code>@​Gekd</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10360">#10360</a> fix(translations): Fix and Improve Spanish translations (<a href="https://github.com/sergioalmela"><code>@​sergioalmela</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10235">#10235</a> fix(theme-translation): add missing German (de) theme.admonition translations (<a href="https://github.com/franzd1"><code>@​franzd1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10342">#10342</a> fix(search): fix algolia search ignore ctrl + F in search input (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="5acbc57bd6"><code>5acbc57</code></a> v3.5.1</li>
<li><a href="daa6b87f24"><code>daa6b87</code></a> chore: release Docusaurus v3.5 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10379">#10379</a>)</li>
<li><a href="afa9fcc965"><code>afa9fcc</code></a> docs(plugin-google-gtag): replace the broken Google Developers links with val...</li>
<li><a href="80203b385d"><code>80203b3</code></a> feat(core): allow plugins to self-disable by returning null (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10286">#10286</a>)</li>
<li><a href="6dd9a5076e"><code>6dd9a50</code></a> chore: simplify TypeScript configs, use TS 5.5 configDir placeholder (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10256">#10256</a>)</li>
<li><a href="dbdd4dfb2e"><code>dbdd4df</code></a> chore: release Docusaurus v3.4 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10186">#10186</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.5.1/packages/docusaurus-plugin-google-gtag">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/plugin-google-gtag&package-manager=npm_and_yarn&previous-version=3.4.0&new-version=3.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-08-12 06:00:52 +00:00
dependabot[bot]
b3775e2feb
⬆️ Bump @docusaurus/module-type-aliases from 3.4.0 to 3.5.1 in /website (#5400)
Bumps [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) from 3.4.0 to 3.5.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/module-type-aliases</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.5.1 (2024-08-09)</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10384">#10384</a> fix(core): algolia context import (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10382">#10382</a> fix(theme-algolia): useDocusaurusContext import error (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 2</h4>
<ul>
<li>Sunghyun Cho (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
<li>Sébastien Lorber (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
<h2>3.5.0 (2024-08-09)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10375">#10375</a> feat(blog): add <code>onUntruncatedBlogPosts</code> blog options (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10376">#10376</a> feat(theme): show unlisted/draft banners in dev mode (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9252">#9252</a> feat(blog): add feed xlst options to render beautiful RSS and Atom feeds (<a href="https://github.com/Xebec19"><code>@​Xebec19</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10216">#10216</a> feat(blog): authors page (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10339">#10339</a> feat(translation): add Estonian default translation (<a href="https://github.com/chirbard"><code>@​chirbard</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10325">#10325</a> feat(translations): Indonesian translation (<a href="https://github.com/priyadi"><code>@​priyadi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10335">#10335</a> feat(mdx-loader): wrap mdx content title (<code># Title</code>) in <code>&lt;header&gt;</code> for concistency (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10222">#10222</a> feat(blog): author header social icons (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-google-analytics</code>, <code>docusaurus-plugin-google-gtag</code>, <code>docusaurus-plugin-google-tag-manager</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-plugin-vercel-analytics</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10286">#10286</a> feat(core): allow plugins to self-disable by returning null (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10252">#10252</a> feat(blog): group sidebar items by year (<code>themeConfig.blog.sidebar.groupByYear</code>) (<a href="https://github.com/alicelovescake"><code>@​alicelovescake</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10224">#10224</a> feat(blog): warn duplicate and inline authors (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils-validation</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10241">#10241</a> feat(mdx): support recma plugins (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10344">#10344</a> fix(translations): fix wrong Estonian (et) translations and typos (<a href="https://github.com/Gekd"><code>@​Gekd</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10360">#10360</a> fix(translations): Fix and Improve Spanish translations (<a href="https://github.com/sergioalmela"><code>@​sergioalmela</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10235">#10235</a> fix(theme-translation): add missing German (de) theme.admonition translations (<a href="https://github.com/franzd1"><code>@​franzd1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10342">#10342</a> fix(search): fix algolia search ignore ctrl + F in search input (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/module-type-aliases</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.5.1 (2024-08-09)</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10384">#10384</a> fix(core): algolia context import (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10382">#10382</a> fix(theme-algolia): useDocusaurusContext import error (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 2</h4>
<ul>
<li>Sunghyun Cho (<a href="https://github.com/anaclumos"><code>@​anaclumos</code></a>)</li>
<li>Sébastien Lorber (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
<h2>3.5.0 (2024-08-09)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10375">#10375</a> feat(blog): add <code>onUntruncatedBlogPosts</code> blog options (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10376">#10376</a> feat(theme): show unlisted/draft banners in dev mode (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9252">#9252</a> feat(blog): add feed xlst options to render beautiful RSS and Atom feeds (<a href="https://github.com/Xebec19"><code>@​Xebec19</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-theme-translations</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10216">#10216</a> feat(blog): authors page (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10339">#10339</a> feat(translation): add Estonian default translation (<a href="https://github.com/chirbard"><code>@​chirbard</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10325">#10325</a> feat(translations): Indonesian translation (<a href="https://github.com/priyadi"><code>@​priyadi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10335">#10335</a> feat(mdx-loader): wrap mdx content title (<code># Title</code>) in <code>&lt;header&gt;</code> for concistency (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10222">#10222</a> feat(blog): author header social icons (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-google-analytics</code>, <code>docusaurus-plugin-google-gtag</code>, <code>docusaurus-plugin-google-tag-manager</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-plugin-vercel-analytics</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10286">#10286</a> feat(core): allow plugins to self-disable by returning null (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10252">#10252</a> feat(blog): group sidebar items by year (<code>themeConfig.blog.sidebar.groupByYear</code>) (<a href="https://github.com/alicelovescake"><code>@​alicelovescake</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10224">#10224</a> feat(blog): warn duplicate and inline authors (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils-validation</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10241">#10241</a> feat(mdx): support recma plugins (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10344">#10344</a> fix(translations): fix wrong Estonian (et) translations and typos (<a href="https://github.com/Gekd"><code>@​Gekd</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10360">#10360</a> fix(translations): Fix and Improve Spanish translations (<a href="https://github.com/sergioalmela"><code>@​sergioalmela</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10235">#10235</a> fix(theme-translation): add missing German (de) theme.admonition translations (<a href="https://github.com/franzd1"><code>@​franzd1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10342">#10342</a> fix(search): fix algolia search ignore ctrl + F in search input (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="5acbc57bd6"><code>5acbc57</code></a> v3.5.1</li>
<li><a href="daa6b87f24"><code>daa6b87</code></a> chore: release Docusaurus v3.5 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10379">#10379</a>)</li>
<li><a href="dbdd4dfb2e"><code>dbdd4df</code></a> chore: release Docusaurus v3.4 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10186">#10186</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.5.1/packages/docusaurus-module-type-aliases">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/module-type-aliases&package-manager=npm_and_yarn&previous-version=3.4.0&new-version=3.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-08-12 05:51:21 +00:00
dependabot[bot]
4fc5b5b146
⬆️ Bump @docusaurus/plugin-google-gtag from 3.3.2 to 3.4.0 in /website (#5347)
Bumps [@docusaurus/plugin-google-gtag](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag) from 3.3.2 to 3.4.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/plugin-google-gtag</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.4.0 (2024-05-31)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10137">#10137</a> feat(docs, blog): add support for <code>tags.yml</code>, predefined list of tags (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10151">#10151</a> feat(theme-translations): Added Turkmen (tk) default theme translations (<a href="https://github.com/ilmedova"><code>@​ilmedova</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10111">#10111</a> feat(theme-translations): Add Bulgarian default theme translations (bg) (<a href="https://github.com/PetarMc1"><code>@​PetarMc1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-theme-search-algolia</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9859">#9859</a> feat(core): hash router option - browse site offline (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10121">#10121</a> feat(core): site storage config options (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10185">#10185</a> fix(docs, blog): Markdown link resolution does not support hot reload (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10178">#10178</a> fix(theme): SearchPage should respect <code>contextualSearch: false</code> setting (<a href="https://github.com/ncoughlin"><code>@​ncoughlin</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10164">#10164</a> fix(search): fix algolia search container bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10168">#10168</a> fix(mdx-loader): resolve Markdown/MDX links with Remark instead of RegExp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10165">#10165</a> fix(theme-translation): add missing Korean (ko) theme translations (<a href="https://github.com/revi"><code>@​revi</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10157">#10157</a> fix(theme-translations): complete Vietnamese theme translations (<a href="https://github.com/namnguyenthanhwork"><code>@​namnguyenthanhwork</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10145">#10145</a> fix(core): fix serve workaround regexp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10142">#10142</a> fix(core): fix <code>docusaurus serve</code> broken for assets when using trailingSlash (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10130">#10130</a> fix(core): the broken anchor checker should not be sensitive pathname trailing slashes (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10144">#10144</a> fix(theme): fix announcement bar layout shift due to missing storage key namespace (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10132">#10132</a> fix(core): <code>configurePostCss()</code> should run after <code>configureWebpack()</code> (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10131">#10131</a> fix(core): codegen should generate unique route prop filenames (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10118">#10118</a> fix(theme-translations): fix missing pluralization for label DocCard.categoryDescription.plurals (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>📝 Documentation</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10176">#10176</a> docs: add community plugin docusaurus-graph (<a href="https://github.com/Arsero"><code>@​Arsero</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10173">#10173</a> docs: improve how to use <code>&lt;details&gt;</code> (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10167">#10167</a> docs: suggest using <code>{&lt;...&gt;...&lt;/...&gt;}</code> if don't use Markdown in migra… (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10143">#10143</a> docs: recommend users to remove hast-util-is-element in migration to v3 (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10124">#10124</a> docs: v3 prepare your site blog post should point users to the upgrade guide (<a href="https://github.com/homotechsual"><code>@​homotechsual</code></a>)</li>
</ul>
<h4>🤖 Dependencies</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10155">#10155</a> chore(deps): bump peaceiris/actions-gh-pages from 3 to 4 (<a href="https://github.com/apps/dependabot"><code>@​dependabot[bot]</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/plugin-google-gtag</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.4.0 (2024-05-31)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10137">#10137</a> feat(docs, blog): add support for <code>tags.yml</code>, predefined list of tags (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10151">#10151</a> feat(theme-translations): Added Turkmen (tk) default theme translations (<a href="https://github.com/ilmedova"><code>@​ilmedova</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10111">#10111</a> feat(theme-translations): Add Bulgarian default theme translations (bg) (<a href="https://github.com/PetarMc1"><code>@​PetarMc1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-theme-search-algolia</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9859">#9859</a> feat(core): hash router option - browse site offline (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10121">#10121</a> feat(core): site storage config options (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10185">#10185</a> fix(docs, blog): Markdown link resolution does not support hot reload (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10178">#10178</a> fix(theme): SearchPage should respect <code>contextualSearch: false</code> setting (<a href="https://github.com/ncoughlin"><code>@​ncoughlin</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10164">#10164</a> fix(search): fix algolia search container bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10168">#10168</a> fix(mdx-loader): resolve Markdown/MDX links with Remark instead of RegExp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10165">#10165</a> fix(theme-translation): add missing Korean (ko) theme translations (<a href="https://github.com/revi"><code>@​revi</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10157">#10157</a> fix(theme-translations): complete Vietnamese theme translations (<a href="https://github.com/namnguyenthanhwork"><code>@​namnguyenthanhwork</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10145">#10145</a> fix(core): fix serve workaround regexp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10142">#10142</a> fix(core): fix <code>docusaurus serve</code> broken for assets when using trailingSlash (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10130">#10130</a> fix(core): the broken anchor checker should not be sensitive pathname trailing slashes (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10144">#10144</a> fix(theme): fix announcement bar layout shift due to missing storage key namespace (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10132">#10132</a> fix(core): <code>configurePostCss()</code> should run after <code>configureWebpack()</code> (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10131">#10131</a> fix(core): codegen should generate unique route prop filenames (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10118">#10118</a> fix(theme-translations): fix missing pluralization for label DocCard.categoryDescription.plurals (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>📝 Documentation</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10176">#10176</a> docs: add community plugin docusaurus-graph (<a href="https://github.com/Arsero"><code>@​Arsero</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10173">#10173</a> docs: improve how to use <code>&lt;details&gt;</code> (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10167">#10167</a> docs: suggest using <code>{&lt;...&gt;...&lt;/...&gt;}</code> if don't use Markdown in migra… (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10143">#10143</a> docs: recommend users to remove hast-util-is-element in migration to v3 (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10124">#10124</a> docs: v3 prepare your site blog post should point users to the upgrade guide (<a href="https://github.com/homotechsual"><code>@​homotechsual</code></a>)</li>
</ul>
<h4>🤖 Dependencies</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10155">#10155</a> chore(deps): bump peaceiris/actions-gh-pages from 3 to 4 (<a href="https://github.com/apps/dependabot"><code>@​dependabot[bot]</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="49e9a21432"><code>49e9a21</code></a> v3.4.0</li>
<li><a href="c125f7a272"><code>c125f7a</code></a> chore: release Docusaurus 3.3.0 + 3.3.1 + 3.3.2 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10101">#10101</a>)</li>
<li><a href="6b53d4263d"><code>6b53d42</code></a> misc: make copyUntypedFiles work for watch mode (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7445">#7445</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.4.0/packages/docusaurus-plugin-google-gtag">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/plugin-google-gtag&package-manager=npm_and_yarn&previous-version=3.3.2&new-version=3.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-06-03 05:46:49 +00:00
dependabot[bot]
0fe2588e78
⬆️ Bump @docusaurus/module-type-aliases from 3.3.2 to 3.4.0 in /website (#5346)
Bumps [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) from 3.3.2 to 3.4.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/module-type-aliases</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.4.0 (2024-05-31)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10137">#10137</a> feat(docs, blog): add support for <code>tags.yml</code>, predefined list of tags (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10151">#10151</a> feat(theme-translations): Added Turkmen (tk) default theme translations (<a href="https://github.com/ilmedova"><code>@​ilmedova</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10111">#10111</a> feat(theme-translations): Add Bulgarian default theme translations (bg) (<a href="https://github.com/PetarMc1"><code>@​PetarMc1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-theme-search-algolia</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9859">#9859</a> feat(core): hash router option - browse site offline (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10121">#10121</a> feat(core): site storage config options (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10185">#10185</a> fix(docs, blog): Markdown link resolution does not support hot reload (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10178">#10178</a> fix(theme): SearchPage should respect <code>contextualSearch: false</code> setting (<a href="https://github.com/ncoughlin"><code>@​ncoughlin</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10164">#10164</a> fix(search): fix algolia search container bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10168">#10168</a> fix(mdx-loader): resolve Markdown/MDX links with Remark instead of RegExp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10165">#10165</a> fix(theme-translation): add missing Korean (ko) theme translations (<a href="https://github.com/revi"><code>@​revi</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10157">#10157</a> fix(theme-translations): complete Vietnamese theme translations (<a href="https://github.com/namnguyenthanhwork"><code>@​namnguyenthanhwork</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10145">#10145</a> fix(core): fix serve workaround regexp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10142">#10142</a> fix(core): fix <code>docusaurus serve</code> broken for assets when using trailingSlash (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10130">#10130</a> fix(core): the broken anchor checker should not be sensitive pathname trailing slashes (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10144">#10144</a> fix(theme): fix announcement bar layout shift due to missing storage key namespace (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10132">#10132</a> fix(core): <code>configurePostCss()</code> should run after <code>configureWebpack()</code> (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10131">#10131</a> fix(core): codegen should generate unique route prop filenames (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10118">#10118</a> fix(theme-translations): fix missing pluralization for label DocCard.categoryDescription.plurals (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>📝 Documentation</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10176">#10176</a> docs: add community plugin docusaurus-graph (<a href="https://github.com/Arsero"><code>@​Arsero</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10173">#10173</a> docs: improve how to use <code>&lt;details&gt;</code> (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10167">#10167</a> docs: suggest using <code>{&lt;...&gt;...&lt;/...&gt;}</code> if don't use Markdown in migra… (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10143">#10143</a> docs: recommend users to remove hast-util-is-element in migration to v3 (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10124">#10124</a> docs: v3 prepare your site blog post should point users to the upgrade guide (<a href="https://github.com/homotechsual"><code>@​homotechsual</code></a>)</li>
</ul>
<h4>🤖 Dependencies</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10155">#10155</a> chore(deps): bump peaceiris/actions-gh-pages from 3 to 4 (<a href="https://github.com/apps/dependabot"><code>@​dependabot[bot]</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/module-type-aliases</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.4.0 (2024-05-31)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>create-docusaurus</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10137">#10137</a> feat(docs, blog): add support for <code>tags.yml</code>, predefined list of tags (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10151">#10151</a> feat(theme-translations): Added Turkmen (tk) default theme translations (<a href="https://github.com/ilmedova"><code>@​ilmedova</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10111">#10111</a> feat(theme-translations): Add Bulgarian default theme translations (bg) (<a href="https://github.com/PetarMc1"><code>@​PetarMc1</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-client-redirects</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-pwa</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-theme-search-algolia</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9859">#9859</a> feat(core): hash router option - browse site offline (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10121">#10121</a> feat(core): site storage config options (experimental) (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10185">#10185</a> fix(docs, blog): Markdown link resolution does not support hot reload (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10178">#10178</a> fix(theme): SearchPage should respect <code>contextualSearch: false</code> setting (<a href="https://github.com/ncoughlin"><code>@​ncoughlin</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10164">#10164</a> fix(search): fix algolia search container bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10168">#10168</a> fix(mdx-loader): resolve Markdown/MDX links with Remark instead of RegExp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10165">#10165</a> fix(theme-translation): add missing Korean (ko) theme translations (<a href="https://github.com/revi"><code>@​revi</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10157">#10157</a> fix(theme-translations): complete Vietnamese theme translations (<a href="https://github.com/namnguyenthanhwork"><code>@​namnguyenthanhwork</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10145">#10145</a> fix(core): fix serve workaround regexp (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10142">#10142</a> fix(core): fix <code>docusaurus serve</code> broken for assets when using trailingSlash (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10130">#10130</a> fix(core): the broken anchor checker should not be sensitive pathname trailing slashes (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10144">#10144</a> fix(theme): fix announcement bar layout shift due to missing storage key namespace (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10132">#10132</a> fix(core): <code>configurePostCss()</code> should run after <code>configureWebpack()</code> (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10131">#10131</a> fix(core): codegen should generate unique route prop filenames (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10118">#10118</a> fix(theme-translations): fix missing pluralization for label DocCard.categoryDescription.plurals (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>📝 Documentation</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10176">#10176</a> docs: add community plugin docusaurus-graph (<a href="https://github.com/Arsero"><code>@​Arsero</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10173">#10173</a> docs: improve how to use <code>&lt;details&gt;</code> (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10167">#10167</a> docs: suggest using <code>{&lt;...&gt;...&lt;/...&gt;}</code> if don't use Markdown in migra… (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10143">#10143</a> docs: recommend users to remove hast-util-is-element in migration to v3 (<a href="https://github.com/tats-u"><code>@​tats-u</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10124">#10124</a> docs: v3 prepare your site blog post should point users to the upgrade guide (<a href="https://github.com/homotechsual"><code>@​homotechsual</code></a>)</li>
</ul>
<h4>🤖 Dependencies</h4>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10155">#10155</a> chore(deps): bump peaceiris/actions-gh-pages from 3 to 4 (<a href="https://github.com/apps/dependabot"><code>@​dependabot[bot]</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="49e9a21432"><code>49e9a21</code></a> v3.4.0</li>
<li><a href="620e46350a"><code>620e463</code></a> feat(core): site storage config options (experimental) (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10121">#10121</a>)</li>
<li><a href="c125f7a272"><code>c125f7a</code></a> chore: release Docusaurus 3.3.0 + 3.3.1 + 3.3.2 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10101">#10101</a>)</li>
<li><a href="53564f33ab"><code>53564f3</code></a> refactor(core): prefetch/preload refactor (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/7282">#7282</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.4.0/packages/docusaurus-module-type-aliases">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/module-type-aliases&package-manager=npm_and_yarn&previous-version=3.3.2&new-version=3.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-06-03 05:41:31 +00:00
Orhan Tozan
d9d993d267
docs: improve definition of a Backup (#5316)
After some building, I realized that a backup is a snapshot of a
resource, not the whole m365 service. Initially, I assumed that 1 backup
takes the whole service per tenant
(https://discord.com/channels/1022200980487557130/1022200981376745474/1231385151376719892).
This doc update should help clear the confusion more.

Maybe there is a better way to word it, so any other suggestions are
welcome.

---

#### Does this PR need a docs update or release note?

- [x]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [x] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E

---------

Co-authored-by: ashmrtn <3891298+ashmrtn@users.noreply.github.com>
2024-05-22 08:57:05 -07:00
dependabot[bot]
1b842a1c60
⬆️ Bump sass from 1.76.0 to 1.77.0 in /website (#5324)
Bumps [sass](https://github.com/sass/dart-sass) from 1.76.0 to 1.77.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.77.0</h2>
<p>To install Sass 1.77.0, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li><em>Don't</em> throw errors for at-rules in keyframe blocks.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1770">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.77.0</h2>
<ul>
<li><em>Don't</em> throw errors for at-rules in keyframe blocks.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="85f39d5ad7"><code>85f39d5</code></a> Allow at-rules in <code>@keyframes</code> blocks (<a href="https://redirect.github.com/sass/dart-sass/issues/2236">#2236</a>)</li>
<li>See full diff in <a href="https://github.com/sass/dart-sass/compare/1.76.0...1.77.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.76.0&new-version=1.77.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-05-07 05:36:17 +00:00
dependabot[bot]
64de1d9e17
⬆️ Bump @docusaurus/plugin-google-gtag from 3.2.0 to 3.3.2 in /website (#5320)
Bumps [@docusaurus/plugin-google-gtag](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag) from 3.2.0 to 3.3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/plugin-google-gtag</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.3.0 (2024-05-03)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-sitemap</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10083">#10083</a> feat: add createSitemapItems hook (<a href="https://github.com/johnnyreilly"><code>@​johnnyreilly</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10064">#10064</a> feat(core): add new site config option <code>siteConfig.markdown.anchors.maintainCase</code> (<a href="https://github.com/iAdramelk"><code>@​iAdramelk</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9767">#9767</a> feat(cli): docusaurus deploy should support a --target-dir option (<a href="https://github.com/SandPod"><code>@​SandPod</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10042">#10042</a> feat(core): simplify plugin API, support route.props (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-pages</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10032">#10032</a> feat(pages): add LastUpdateAuthor &amp; LastUpdateTime &amp; editUrl (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-cssnano-preset</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10092">#10092</a> chore: Upgrade svgr / svgo / cssnano (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10091">#10091</a> fix(theme): <code>&lt;Tabs&gt;</code> props should allow overriding defaults (<a href="https://github.com/gagdiez"><code>@​gagdiez</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10080">#10080</a> fix(theme): <code>&lt;Admonition&gt;</code> should render properly without heading/icon (<a href="https://github.com/andrmaz"><code>@​andrmaz</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10090">#10090</a> fix(core): <code>docusaurus serve</code> redirects should include the site <code>/baseUrl/</code> prefix (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-preset-classic</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-live-codeblock</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10079">#10079</a> fix: handle React v18.3 warnings (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10070">#10070</a> fix(theme-translations): add missing theme translations for pt-BR (<a href="https://github.com/h3nr1ke"><code>@​h3nr1ke</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10051">#10051</a> fix(theme-translations): correct label for tip admonition in italian (<a href="https://github.com/tomsotte"><code>@​tomsotte</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10048">#10048</a> fix(algolia): add insights property on Algolia Theme Config object TS definition (<a href="https://github.com/Virgil993"><code>@​Virgil993</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10054">#10054</a> fix(core): sortRoutes shouldn't have a default baseUrl value, this led to a bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10025">#10025</a> fix(docs): sidebar item label impact the pagination label of docs (<a href="https://github.com/Abdullah-03"><code>@​Abdullah-03</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10022">#10022</a> fix(utils): getFileCommitDate should support <code>log.showSignature=true</code> (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<ul>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10060">#10060</a> refactor(core): optimize App entrypoint, it should not re-render when navigating (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>💅 Polish</h4>
<ul>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10061">#10061</a> refactor(theme): simplify CSS solution to solve empty search container (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10023">#10023</a> refactor(website): refactor showcase components (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/plugin-google-gtag</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.3.2 (2024-05-03)</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10103">#10103</a> fix(core): do not recreate ReactDOM Root, fix React warning on hot reload (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 1</h4>
<ul>
<li>Sébastien Lorber (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
<h2>3.3.1 (2024-05-03)</h2>
<p>Failed release</p>
<h2>3.3.0 (2024-05-03)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-sitemap</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10083">#10083</a> feat: add createSitemapItems hook (<a href="https://github.com/johnnyreilly"><code>@​johnnyreilly</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10064">#10064</a> feat(core): add new site config option <code>siteConfig.markdown.anchors.maintainCase</code> (<a href="https://github.com/iAdramelk"><code>@​iAdramelk</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9767">#9767</a> feat(cli): docusaurus deploy should support a --target-dir option (<a href="https://github.com/SandPod"><code>@​SandPod</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10042">#10042</a> feat(core): simplify plugin API, support route.props (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-pages</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10032">#10032</a> feat(pages): add LastUpdateAuthor &amp; LastUpdateTime &amp; editUrl (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-cssnano-preset</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10092">#10092</a> chore: Upgrade svgr / svgo / cssnano (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10091">#10091</a> fix(theme): <code>&lt;Tabs&gt;</code> props should allow overriding defaults (<a href="https://github.com/gagdiez"><code>@​gagdiez</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10080">#10080</a> fix(theme): <code>&lt;Admonition&gt;</code> should render properly without heading/icon (<a href="https://github.com/andrmaz"><code>@​andrmaz</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10090">#10090</a> fix(core): <code>docusaurus serve</code> redirects should include the site <code>/baseUrl/</code> prefix (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-preset-classic</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-live-codeblock</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10079">#10079</a> fix: handle React v18.3 warnings (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10070">#10070</a> fix(theme-translations): add missing theme translations for pt-BR (<a href="https://github.com/h3nr1ke"><code>@​h3nr1ke</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10051">#10051</a> fix(theme-translations): correct label for tip admonition in italian (<a href="https://github.com/tomsotte"><code>@​tomsotte</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10048">#10048</a> fix(algolia): add insights property on Algolia Theme Config object TS definition (<a href="https://github.com/Virgil993"><code>@​Virgil993</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10054">#10054</a> fix(core): sortRoutes shouldn't have a default baseUrl value, this led to a bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10025">#10025</a> fix(docs): sidebar item label impact the pagination label of docs (<a href="https://github.com/Abdullah-03"><code>@​Abdullah-03</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="bc638d674b"><code>bc638d6</code></a> v3.3.2</li>
<li><a href="f3524cf332"><code>f3524cf</code></a> v3.3.1</li>
<li><a href="2ec4e078b5"><code>2ec4e07</code></a> v3.3.0</li>
<li><a href="f88da6c66d"><code>f88da6c</code></a> refactor: extract base TS client config + upgrade TS + refactor TS setup (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10">#10</a>...</li>
<li><a href="e012e03158"><code>e012e03</code></a> chore: release Docusaurus 3.2.1 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10016">#10016</a>)</li>
<li><a href="debfc87d34"><code>debfc87</code></a> chore: release Docusaurus v3.2.0 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/10000">#10000</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.3.2/packages/docusaurus-plugin-google-gtag">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/plugin-google-gtag&package-manager=npm_and_yarn&previous-version=3.2.0&new-version=3.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-05-06 06:17:05 +00:00
dependabot[bot]
48c0ab5175
⬆️ Bump @docusaurus/module-type-aliases from 3.2.0 to 3.3.2 in /website (#5317)
Bumps [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) from 3.2.0 to 3.3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/module-type-aliases</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.3.0 (2024-05-03)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-sitemap</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10083">#10083</a> feat: add createSitemapItems hook (<a href="https://github.com/johnnyreilly"><code>@​johnnyreilly</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10064">#10064</a> feat(core): add new site config option <code>siteConfig.markdown.anchors.maintainCase</code> (<a href="https://github.com/iAdramelk"><code>@​iAdramelk</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9767">#9767</a> feat(cli): docusaurus deploy should support a --target-dir option (<a href="https://github.com/SandPod"><code>@​SandPod</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10042">#10042</a> feat(core): simplify plugin API, support route.props (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-pages</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10032">#10032</a> feat(pages): add LastUpdateAuthor &amp; LastUpdateTime &amp; editUrl (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-cssnano-preset</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10092">#10092</a> chore: Upgrade svgr / svgo / cssnano (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10091">#10091</a> fix(theme): <code>&lt;Tabs&gt;</code> props should allow overriding defaults (<a href="https://github.com/gagdiez"><code>@​gagdiez</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10080">#10080</a> fix(theme): <code>&lt;Admonition&gt;</code> should render properly without heading/icon (<a href="https://github.com/andrmaz"><code>@​andrmaz</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10090">#10090</a> fix(core): <code>docusaurus serve</code> redirects should include the site <code>/baseUrl/</code> prefix (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-preset-classic</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-live-codeblock</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10079">#10079</a> fix: handle React v18.3 warnings (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10070">#10070</a> fix(theme-translations): add missing theme translations for pt-BR (<a href="https://github.com/h3nr1ke"><code>@​h3nr1ke</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10051">#10051</a> fix(theme-translations): correct label for tip admonition in italian (<a href="https://github.com/tomsotte"><code>@​tomsotte</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10048">#10048</a> fix(algolia): add insights property on Algolia Theme Config object TS definition (<a href="https://github.com/Virgil993"><code>@​Virgil993</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10054">#10054</a> fix(core): sortRoutes shouldn't have a default baseUrl value, this led to a bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10025">#10025</a> fix(docs): sidebar item label impact the pagination label of docs (<a href="https://github.com/Abdullah-03"><code>@​Abdullah-03</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10022">#10022</a> fix(utils): getFileCommitDate should support <code>log.showSignature=true</code> (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<ul>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10060">#10060</a> refactor(core): optimize App entrypoint, it should not re-render when navigating (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>💅 Polish</h4>
<ul>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10061">#10061</a> refactor(theme): simplify CSS solution to solve empty search container (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10023">#10023</a> refactor(website): refactor showcase components (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/module-type-aliases</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.3.2 (2024-05-03)</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10103">#10103</a> fix(core): do not recreate ReactDOM Root, fix React warning on hot reload (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 1</h4>
<ul>
<li>Sébastien Lorber (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
<h2>3.3.1 (2024-05-03)</h2>
<p>Failed release</p>
<h2>3.3.0 (2024-05-03)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-sitemap</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10083">#10083</a> feat: add createSitemapItems hook (<a href="https://github.com/johnnyreilly"><code>@​johnnyreilly</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10064">#10064</a> feat(core): add new site config option <code>siteConfig.markdown.anchors.maintainCase</code> (<a href="https://github.com/iAdramelk"><code>@​iAdramelk</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9767">#9767</a> feat(cli): docusaurus deploy should support a --target-dir option (<a href="https://github.com/SandPod"><code>@​SandPod</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10042">#10042</a> feat(core): simplify plugin API, support route.props (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-pages</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10032">#10032</a> feat(pages): add LastUpdateAuthor &amp; LastUpdateTime &amp; editUrl (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-cssnano-preset</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10092">#10092</a> chore: Upgrade svgr / svgo / cssnano (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10091">#10091</a> fix(theme): <code>&lt;Tabs&gt;</code> props should allow overriding defaults (<a href="https://github.com/gagdiez"><code>@​gagdiez</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10080">#10080</a> fix(theme): <code>&lt;Admonition&gt;</code> should render properly without heading/icon (<a href="https://github.com/andrmaz"><code>@​andrmaz</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10090">#10090</a> fix(core): <code>docusaurus serve</code> redirects should include the site <code>/baseUrl/</code> prefix (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-module-type-aliases</code>, <code>docusaurus-preset-classic</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-live-codeblock</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10079">#10079</a> fix: handle React v18.3 warnings (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10070">#10070</a> fix(theme-translations): add missing theme translations for pt-BR (<a href="https://github.com/h3nr1ke"><code>@​h3nr1ke</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10051">#10051</a> fix(theme-translations): correct label for tip admonition in italian (<a href="https://github.com/tomsotte"><code>@​tomsotte</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10048">#10048</a> fix(algolia): add insights property on Algolia Theme Config object TS definition (<a href="https://github.com/Virgil993"><code>@​Virgil993</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10054">#10054</a> fix(core): sortRoutes shouldn't have a default baseUrl value, this led to a bug (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-docs</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/10025">#10025</a> fix(docs): sidebar item label impact the pagination label of docs (<a href="https://github.com/Abdullah-03"><code>@​Abdullah-03</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="bc638d674b"><code>bc638d6</code></a> v3.3.2</li>
<li><a href="f3524cf332"><code>f3524cf</code></a> v3.3.1</li>
<li><a href="3490433f94"><code>3490433</code></a> Merge branch 'main' into slorber/docusaurus-v3.3</li>
<li><a href="2d8281fc03"><code>2d8281f</code></a> fix(core): do not recreate ReactDOM Root, fix React warning on hot reload (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/1">#1</a>...</li>
<li><a href="2ec4e078b5"><code>2ec4e07</code></a> v3.3.0</li>
<li><a href="ca33858ca0"><code>ca33858</code></a> fix: handle React v18.3 warnings (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10079">#10079</a>)</li>
<li><a href="e012e03158"><code>e012e03</code></a> chore: release Docusaurus 3.2.1 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10016">#10016</a>)</li>
<li><a href="debfc87d34"><code>debfc87</code></a> chore: release Docusaurus v3.2.0 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/10000">#10000</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.3.2/packages/docusaurus-module-type-aliases">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/module-type-aliases&package-manager=npm_and_yarn&previous-version=3.2.0&new-version=3.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-05-06 06:06:58 +00:00
dependabot[bot]
eb3ab3aebc
⬆️ Bump sass from 1.75.0 to 1.76.0 in /website (#5313)
Bumps [sass](https://github.com/sass/dart-sass) from 1.75.0 to 1.76.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.76.0</h2>
<p>To install Sass 1.76.0, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>
<p>Throw errors for misplaced statements in keyframe blocks.</p>
</li>
<li>
<p>Mixins and functions whose names begin with <code>--</code> are now deprecated for forwards-compatibility with the in-progress CSS functions and mixins spec. This deprecation is named <code>css-function-mixin</code>.</p>
</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1760">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.76.0</h2>
<ul>
<li>
<p>Throw errors for misplaced statements in keyframe blocks.</p>
</li>
<li>
<p>Mixins and functions whose names begin with <code>--</code> are now deprecated for
forwards-compatibility with the in-progress CSS functions and mixins spec.
This deprecation is named <code>css-function-mixin</code>.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="264b2d58b0"><code>264b2d5</code></a> Deprecate function and mixin names beginning with <code>--</code> (<a href="https://redirect.github.com/sass/dart-sass/issues/2230">#2230</a>)</li>
<li><a href="f145e1c11b"><code>f145e1c</code></a> Throw errors for misplaced statements in keyframe blocks (<a href="https://redirect.github.com/sass/dart-sass/issues/2226">#2226</a>)</li>
<li><a href="eafc279ae7"><code>eafc279</code></a> Explicitly add a breaking change exemption for invalid CSS output (<a href="https://redirect.github.com/sass/dart-sass/issues/2225">#2225</a>)</li>
<li><a href="b97f26f71f"><code>b97f26f</code></a> Add a per-importer cache for loads that aren't cacheable en masse (<a href="https://redirect.github.com/sass/dart-sass/issues/2219">#2219</a>)</li>
<li><a href="2a9eaadefa"><code>2a9eaad</code></a> Implement access tracking for containingUrl (<a href="https://redirect.github.com/sass/dart-sass/issues/2220">#2220</a>)</li>
<li>See full diff in <a href="https://github.com/sass/dart-sass/compare/1.75.0...1.76.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.75.0&new-version=1.76.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-05-01 05:12:33 +00:00
dependabot[bot]
df423d5e18
⬆️ Bump react-dom from 18.2.0 to 18.3.0 in /website (#5307)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 18.2.0 to 18.3.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/facebook/react/commits/HEAD/packages/react-dom">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~react-bot">react-bot</a>, a new releaser for react-dom since your current version.</p>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=react-dom&package-manager=npm_and_yarn&previous-version=18.2.0&new-version=18.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-04-26 05:17:28 +00:00
Abhishek Pandey
23de1d53dd
Skip more conv tests (#5302)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-04-23 05:38:51 +00:00
Abhishek Pandey
963dd4a11d
Disable conversations integ tests (#5299)
<!-- PR description-->
`CorsoCITeam` group mailbox backup is currently broken because of invalid `odata.NextLink` which causes an infinite loop during paging. Disabling conv backups while we go fix the impacted group mailbox.



---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-04-18 05:51:21 +00:00
dependabot[bot]
e96f74e634
⬆️ Bump sass from 1.74.1 to 1.75.0 in /website (#5297)
Bumps [sass](https://github.com/sass/dart-sass) from 1.74.1 to 1.75.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.75.0</h2>
<p>To install Sass 1.75.0, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>Fix a bug in which stylesheet canonicalization could be cached incorrectly when custom importers or the Node.js package importer made decisions based on the URL of the containing stylesheet.</li>
</ul>
<h3>JS API</h3>
<ul>
<li>Allow <code>importer</code> to be passed without <code>url</code> in <code>StringOptionsWithImporter</code>.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1750">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.75.0</h2>
<ul>
<li>Fix a bug in which stylesheet canonicalization could be cached incorrectly
when custom importers or the Node.js package importer made decisions based on
the URL of the containing stylesheet.</li>
</ul>
<h3>JS API</h3>
<ul>
<li>Allow <code>importer</code> to be passed without <code>url</code> in <code>StringOptionsWithImporter</code>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="821b98e26c"><code>821b98e</code></a> Don't cache canonicalize calls when <code>containingUrl</code> is available (<a href="https://redirect.github.com/sass/dart-sass/issues/2215">#2215</a>)</li>
<li><a href="c5aff1b2f2"><code>c5aff1b</code></a> Make it possible to build npm with a linked language repo (<a href="https://redirect.github.com/sass/dart-sass/issues/2214">#2214</a>)</li>
<li>See full diff in <a href="https://github.com/sass/dart-sass/compare/1.74.1...1.75.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.74.1&new-version=1.75.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-04-12 05:09:36 +00:00
Keepers
b180dee597
adding retries to purge action powershell scripts (#5294)
#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 💻 CI/Deployment

#### Test Plan

- [x] 💚 E2E
2024-04-09 18:32:37 +00:00
dependabot[bot]
44d4821a8d
⬆️ Bump sass from 1.72.0 to 1.74.1 in /website (#5286)
Bumps [sass](https://github.com/sass/dart-sass) from 1.72.0 to 1.74.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.74.1</h2>
<p>To install Sass 1.74.1, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>No user-visible changes.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1741">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.74.1</h2>
<ul>
<li>No user-visible changes.</li>
</ul>
<h2>1.74.0</h2>
<h3>JS API</h3>
<ul>
<li>
<p>Add a new top-level <code>deprecations</code> object, which contains various
<code>Deprecation</code> objects that define the different types of deprecation used by
the Sass compiler and can be passed to the options below.</p>
</li>
<li>
<p>Add a new <code>fatalDeprecations</code> compiler option that causes the compiler to
error if any deprecation warnings of the provided types are encountered. You
can also pass in a <code>Version</code> object to treat all deprecations that were active
in that Dart Sass version as fatal.</p>
</li>
<li>
<p>Add a new <code>futureDeprecations</code> compiler option that allows you to opt-in to
certain deprecations early (currently just <code>import</code>).</p>
</li>
<li>
<p>Add a new <code>silenceDeprecations</code> compiler option to ignore any deprecation
warnings of the provided types.</p>
</li>
</ul>
<h3>Command-Line Interface</h3>
<ul>
<li>
<p>Add a new <code>--silence-deprecation</code> flag, which causes the compiler to ignore
any deprecation warnings of the provided types.</p>
</li>
<li>
<p>Previously, if a future deprecation was passed to <code>--fatal-deprecation</code> but
not <code>--future-deprecation</code>, it would be treated as fatal despite not being
enabled. Both flags are now required to treat a future deprecation as fatal
with a warning emitted if <code>--fatal-deprecation</code> is passed without
<code>--future-deprecation</code>, matching the JS API's behavior.</p>
</li>
</ul>
<h3>Dart API</h3>
<ul>
<li>
<p>The <code>compile</code> methods now take in a <code>silenceDeprecations</code> parameter, which
causes the compiler to ignore any deprecation warnings of the provided types.</p>
</li>
<li>
<p>Add <code>Deprecation.obsoleteIn</code> to match the JS API. This is currently null for
all deprecations, but will be used once some deprecations become obsolete in
Dart Sass 2.0.0.</p>
</li>
<li>
<p><strong>Potentially breaking bug fix:</strong> Fix a bug where <code>compileStringToResultAsync</code>
ignored <code>fatalDeprecations</code> and <code>futureDeprecations</code>.</p>
</li>
<li>
<p>The behavior around making future deprecations fatal mentioned in the CLI
section above has also been changed in the Dart API.</p>
</li>
</ul>
<h2>1.73.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="1137797f17"><code>1137797</code></a> Fix bulma and release 1.74.1 (<a href="https://redirect.github.com/sass/dart-sass/issues/2210">#2210</a>)</li>
<li><a href="d9220d9c37"><code>d9220d9</code></a> Complete implementation the deprecations API (<a href="https://redirect.github.com/sass/dart-sass/issues/2207">#2207</a>)</li>
<li><a href="783c248d2f"><code>783c248</code></a> Fix typo in function documentation (<a href="https://redirect.github.com/sass/dart-sass/issues/2205">#2205</a>)</li>
<li><a href="c8d064368c"><code>c8d0643</code></a> Better handle filesystem importers when load paths aren't necessary (<a href="https://redirect.github.com/sass/dart-sass/issues/2203">#2203</a>)</li>
<li><a href="9302b3519c"><code>9302b35</code></a> Add support for nesting in plain CSS (<a href="https://redirect.github.com/sass/dart-sass/issues/2198">#2198</a>)</li>
<li><a href="772280a7ff"><code>772280a</code></a> Support linux-riscv64 and windows-arm64 (<a href="https://redirect.github.com/sass/dart-sass/issues/2201">#2201</a>)</li>
<li>See full diff in <a href="https://github.com/sass/dart-sass/compare/1.72.0...1.74.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.72.0&new-version=1.74.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-04-04 05:58:44 +00:00
dependabot[bot]
6bbb46b29a
⬆️ Bump @docusaurus/plugin-google-gtag from 3.1.1 to 3.2.0 in /website (#5284)
Bumps [@docusaurus/plugin-google-gtag](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag) from 3.1.1 to 3.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/plugin-google-gtag</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.2.0 (2024-03-29)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9954">#9954</a> feat(sitemap): add support for &quot;lastmod&quot; (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9912">#9912</a> feat(blog): add LastUpdateAuthor &amp; LastUpdateTime (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9931">#9931</a> feat(core): add new plugin allContentLoaded lifecycle (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9928">#9928</a> feat(theme-translations) Icelandic (is) (<a href="https://github.com/Hallinn"><code>@​Hallinn</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9886">#9886</a> feat(blog): allow processing blog posts through a processBlogPosts function (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9838">#9838</a> feat(blog): add blog pageBasePath plugin option (<a href="https://github.com/ilg-ul"><code>@​ilg-ul</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9681">#9681</a> feat(swizzle): ask user preferred language if no language CLI option provided (<a href="https://github.com/yixiaojiu"><code>@​yixiaojiu</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9442">#9442</a> feat(create-docusaurus): ask user for preferred language when no language CLI option provided (<a href="https://github.com/Rafael-Martins"><code>@​Rafael-Martins</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-vercel-analytics</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9687">#9687</a> feat(plugin-vercel-analytics): add new vercel analytics plugin (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9684">#9684</a> feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials (<a href="https://github.com/anatolykopyl"><code>@​anatolykopyl</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9999">#9999</a> fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9945">#9945</a> fix(a11y): move focus algolia-search focus back to search input on Escape (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9920">#9920</a> fix(blog): apply trailing slash to blog feed (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9944">#9944</a> fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9915">#9915</a> fix(theme-translations): complete and modify Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9910">#9910</a> fix(theme-translations): add Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9872">#9872</a> fix(theme-translations): complete and improve Spanish theme translations (<a href="https://github.com/4troDev"><code>@​4troDev</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9812">#9812</a> fix(i18n): add missing theme translations for fa locale (<a href="https://github.com/VahidNaderi"><code>@​VahidNaderi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9897">#9897</a> fix(mdx-loader): mdx-code-block should support CRLF (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9878">#9878</a> fix(core): fix default i18n calendar used, infer it from locale if possible (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9852">#9852</a> fix(core): ensure core error boundary is able to render theme layout (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-remark-plugin-npm2yarn</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9861">#9861</a> fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9851">#9851</a> fix(theme-classic): should use plurals for category items description (<a href="https://github.com/baradusov"><code>@​baradusov</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/plugin-google-gtag</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.2.0 (2024-03-29)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9954">#9954</a> feat(sitemap): add support for &quot;lastmod&quot; (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9912">#9912</a> feat(blog): add LastUpdateAuthor &amp; LastUpdateTime (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9931">#9931</a> feat(core): add new plugin allContentLoaded lifecycle (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9928">#9928</a> feat(theme-translations) Icelandic (is) (<a href="https://github.com/Hallinn"><code>@​Hallinn</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9886">#9886</a> feat(blog): allow processing blog posts through a processBlogPosts function (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9838">#9838</a> feat(blog): add blog pageBasePath plugin option (<a href="https://github.com/ilg-ul"><code>@​ilg-ul</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9681">#9681</a> feat(swizzle): ask user preferred language if no language CLI option provided (<a href="https://github.com/yixiaojiu"><code>@​yixiaojiu</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9442">#9442</a> feat(create-docusaurus): ask user for preferred language when no language CLI option provided (<a href="https://github.com/Rafael-Martins"><code>@​Rafael-Martins</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-vercel-analytics</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9687">#9687</a> feat(plugin-vercel-analytics): add new vercel analytics plugin (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9684">#9684</a> feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials (<a href="https://github.com/anatolykopyl"><code>@​anatolykopyl</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9999">#9999</a> fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9945">#9945</a> fix(a11y): move focus algolia-search focus back to search input on Escape (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9920">#9920</a> fix(blog): apply trailing slash to blog feed (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9944">#9944</a> fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9915">#9915</a> fix(theme-translations): complete and modify Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9910">#9910</a> fix(theme-translations): add Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9872">#9872</a> fix(theme-translations): complete and improve Spanish theme translations (<a href="https://github.com/4troDev"><code>@​4troDev</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9812">#9812</a> fix(i18n): add missing theme translations for fa locale (<a href="https://github.com/VahidNaderi"><code>@​VahidNaderi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9897">#9897</a> fix(mdx-loader): mdx-code-block should support CRLF (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9878">#9878</a> fix(core): fix default i18n calendar used, infer it from locale if possible (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9852">#9852</a> fix(core): ensure core error boundary is able to render theme layout (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-remark-plugin-npm2yarn</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9861">#9861</a> fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9851">#9851</a> fix(theme-classic): should use plurals for category items description (<a href="https://github.com/baradusov"><code>@​baradusov</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="5af143651b"><code>5af1436</code></a> v3.2.0</li>
<li><a href="49ecd8f472"><code>49ecd8f</code></a> fix(gtag): send the newly rendered page's title instead of the old one's (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7424">#7424</a>)</li>
<li><a href="47a2cca17d"><code>47a2cca</code></a> chore: require Node 16.14 (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7501">#7501</a>)</li>
<li><a href="bf1513a3e3"><code>bf1513a</code></a> refactor: fix a lot of errors in type-aware linting (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7477">#7477</a>)</li>
<li><a href="6b53d4263d"><code>6b53d42</code></a> misc: make copyUntypedFiles work for watch mode (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7445">#7445</a>)</li>
<li><a href="a555fd1dcb"><code>a555fd1</code></a> refactor: make each tsconfig explicitly declare module and include/exclude (#...</li>
<li><a href="7613ecb9ea"><code>7613ecb</code></a> refactor: use TS project references instead of running tsc multiple times (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7">#7</a>...</li>
<li><a href="26df8c83ce"><code>26df8c8</code></a> chore: prepare v2.0.0-beta.20 release (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7347">#7347</a>)</li>
<li><a href="6fa51890f0"><code>6fa5189</code></a> chore: prepare v2.0.0-beta.19 release (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-google-gtag/issues/7325">#7325</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.2.0/packages/docusaurus-plugin-google-gtag">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/plugin-google-gtag&package-manager=npm_and_yarn&previous-version=3.1.1&new-version=3.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-04-01 05:42:47 +00:00
dependabot[bot]
f197d7cf7b
⬆️ Bump @docusaurus/module-type-aliases from 3.1.1 to 3.2.0 in /website (#5281)
Bumps [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) from 3.1.1 to 3.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/releases"><code>@​docusaurus/module-type-aliases</code>'s releases</a>.</em></p>
<blockquote>
<h2>3.2.0 (2024-03-29)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9954">#9954</a> feat(sitemap): add support for &quot;lastmod&quot; (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9912">#9912</a> feat(blog): add LastUpdateAuthor &amp; LastUpdateTime (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9931">#9931</a> feat(core): add new plugin allContentLoaded lifecycle (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9928">#9928</a> feat(theme-translations) Icelandic (is) (<a href="https://github.com/Hallinn"><code>@​Hallinn</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9886">#9886</a> feat(blog): allow processing blog posts through a processBlogPosts function (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9838">#9838</a> feat(blog): add blog pageBasePath plugin option (<a href="https://github.com/ilg-ul"><code>@​ilg-ul</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9681">#9681</a> feat(swizzle): ask user preferred language if no language CLI option provided (<a href="https://github.com/yixiaojiu"><code>@​yixiaojiu</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9442">#9442</a> feat(create-docusaurus): ask user for preferred language when no language CLI option provided (<a href="https://github.com/Rafael-Martins"><code>@​Rafael-Martins</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-vercel-analytics</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9687">#9687</a> feat(plugin-vercel-analytics): add new vercel analytics plugin (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9684">#9684</a> feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials (<a href="https://github.com/anatolykopyl"><code>@​anatolykopyl</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9999">#9999</a> fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9945">#9945</a> fix(a11y): move focus algolia-search focus back to search input on Escape (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9920">#9920</a> fix(blog): apply trailing slash to blog feed (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9944">#9944</a> fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9915">#9915</a> fix(theme-translations): complete and modify Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9910">#9910</a> fix(theme-translations): add Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9872">#9872</a> fix(theme-translations): complete and improve Spanish theme translations (<a href="https://github.com/4troDev"><code>@​4troDev</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9812">#9812</a> fix(i18n): add missing theme translations for fa locale (<a href="https://github.com/VahidNaderi"><code>@​VahidNaderi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9897">#9897</a> fix(mdx-loader): mdx-code-block should support CRLF (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9878">#9878</a> fix(core): fix default i18n calendar used, infer it from locale if possible (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9852">#9852</a> fix(core): ensure core error boundary is able to render theme layout (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-remark-plugin-npm2yarn</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9861">#9861</a> fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9851">#9851</a> fix(theme-classic): should use plurals for category items description (<a href="https://github.com/baradusov"><code>@​baradusov</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md"><code>@​docusaurus/module-type-aliases</code>'s changelog</a>.</em></p>
<blockquote>
<h2>3.2.0 (2024-03-29)</h2>
<h4>🚀 New Feature</h4>
<ul>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-plugin-content-pages</code>, <code>docusaurus-plugin-sitemap</code>, <code>docusaurus-types</code>, <code>docusaurus-utils</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9954">#9954</a> feat(sitemap): add support for &quot;lastmod&quot; (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>, <code>docusaurus-plugin-content-docs</code>, <code>docusaurus-theme-classic</code>, <code>docusaurus-theme-common</code>, <code>docusaurus-utils-validation</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9912">#9912</a> feat(blog): add LastUpdateAuthor &amp; LastUpdateTime (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-debug</code>, <code>docusaurus-types</code>, <code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9931">#9931</a> feat(core): add new plugin allContentLoaded lifecycle (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9928">#9928</a> feat(theme-translations) Icelandic (is) (<a href="https://github.com/Hallinn"><code>@​Hallinn</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9886">#9886</a> feat(blog): allow processing blog posts through a processBlogPosts function (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9838">#9838</a> feat(blog): add blog pageBasePath plugin option (<a href="https://github.com/ilg-ul"><code>@​ilg-ul</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9681">#9681</a> feat(swizzle): ask user preferred language if no language CLI option provided (<a href="https://github.com/yixiaojiu"><code>@​yixiaojiu</code></a>)</li>
</ul>
</li>
<li><code>create-docusaurus</code>, <code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9442">#9442</a> feat(create-docusaurus): ask user for preferred language when no language CLI option provided (<a href="https://github.com/Rafael-Martins"><code>@​Rafael-Martins</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-vercel-analytics</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9687">#9687</a> feat(plugin-vercel-analytics): add new vercel analytics plugin (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9684">#9684</a> feat(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials (<a href="https://github.com/anatolykopyl"><code>@​anatolykopyl</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>docusaurus-mdx-loader</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9999">#9999</a> fix(mdx-loader): Ignore contentTitle coming after Markdown thematicBreak (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-search-algolia</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9945">#9945</a> fix(a11y): move focus algolia-search focus back to search input on Escape (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-plugin-content-blog</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9920">#9920</a> fix(blog): apply trailing slash to blog feed (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9944">#9944</a> fix(theme): improve a11y of DocSidebarItemCategory expand/collapsed button (<a href="https://github.com/mxschmitt"><code>@​mxschmitt</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9915">#9915</a> fix(theme-translations): complete and modify Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9910">#9910</a> fix(theme-translations): add Japanese translations (<a href="https://github.com/Suenaga-Ryuya"><code>@​Suenaga-Ryuya</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9872">#9872</a> fix(theme-translations): complete and improve Spanish theme translations (<a href="https://github.com/4troDev"><code>@​4troDev</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9812">#9812</a> fix(i18n): add missing theme translations for fa locale (<a href="https://github.com/VahidNaderi"><code>@​VahidNaderi</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-utils</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9897">#9897</a> fix(mdx-loader): mdx-code-block should support CRLF (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9878">#9878</a> fix(core): fix default i18n calendar used, infer it from locale if possible (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9852">#9852</a> fix(core): ensure core error boundary is able to render theme layout (<a href="https://github.com/slorber"><code>@​slorber</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-remark-plugin-npm2yarn</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9861">#9861</a> fix(remark-npm2yarn): update npm-to-yarn from 2.0.0 to 2.2.1, fix pnpm extra args syntax (<a href="https://github.com/OzakIOne"><code>@​OzakIOne</code></a>)</li>
</ul>
</li>
<li><code>docusaurus-theme-classic</code>, <code>docusaurus-theme-translations</code>
<ul>
<li><a href="https://redirect.github.com/facebook/docusaurus/pull/9851">#9851</a> fix(theme-classic): should use plurals for category items description (<a href="https://github.com/baradusov"><code>@​baradusov</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="5af143651b"><code>5af1436</code></a> v3.2.0</li>
<li><a href="4388267c26"><code>4388267</code></a> fix(core): various broken anchor link fixes (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/9732">#9732</a>)</li>
<li><a href="fd49301a45"><code>fd49301</code></a> feat(core): make broken link checker detect broken anchors - add `onBrokenAnc...</li>
<li><a href="8dd1e13f2a"><code>8dd1e13</code></a> fix(type-aliases): add <code>title</code> prop for imported inline SVG React components ...</li>
<li><a href="fa1ce230ea"><code>fa1ce23</code></a> refactor: capitalize comments (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/7188">#7188</a>)</li>
<li><a href="171927342f"><code>1719273</code></a> feat(core): fail-safe global data fetching (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/7083">#7083</a>)</li>
<li><a href="24c205a835"><code>24c205a</code></a> refactor: replace non-prop interface with type; allow plugin lifecycles to ha...</li>
<li><a href="3f33e90704"><code>3f33e90</code></a> chore: upgrade dependencies (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/7065">#7065</a>)</li>
<li><a href="77662260f8"><code>7766226</code></a> refactor(core): refactor routes generation logic (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/7054">#7054</a>)</li>
<li><a href="5fb09a2946"><code>5fb09a2</code></a> refactor(core): reorganize files (<a href="https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases/issues/7042">#7042</a>)</li>
<li>See full diff in <a href="https://github.com/facebook/docusaurus/commits/v3.2.0/packages/docusaurus-module-type-aliases">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@docusaurus/module-type-aliases&package-manager=npm_and_yarn&previous-version=3.1.1&new-version=3.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-04-01 05:38:16 +00:00
dependabot[bot]
6c9de9bef3
⬆️ Bump express from 4.18.2 to 4.19.2 in /website (#5276)
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/expressjs/express/releases">express's releases</a>.</em></p>
<blockquote>
<h2>4.19.2</h2>
<h2>What's Changed</h2>
<ul>
<li><a href="0b746953c4">Improved fix for open redirect allow list bypass</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/expressjs/express/compare/4.19.1...4.19.2">https://github.com/expressjs/express/compare/4.19.1...4.19.2</a></p>
<h2>4.19.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix ci after location patch by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5552">expressjs/express#5552</a></li>
<li>fixed un-edited version in history.md for 4.19.0 by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5556">expressjs/express#5556</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/expressjs/express/compare/4.19.0...4.19.1">https://github.com/expressjs/express/compare/4.19.0...4.19.1</a></p>
<h2>4.19.0</h2>
<h2>What's Changed</h2>
<ul>
<li>fix typo in release date by <a href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5527">expressjs/express#5527</a></li>
<li>docs: nominating <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> to be project captian by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5511">expressjs/express#5511</a></li>
<li>docs: loosen TC activity rules by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5510">expressjs/express#5510</a></li>
<li>Add note on how to update docs for new release by <a href="https://github.com/crandmck"><code>@​crandmck</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5541">expressjs/express#5541</a></li>
<li><a href="660ccf5fa3">Prevent open redirect allow list bypass due to encodeurl</a></li>
<li>Release 4.19.0 by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5551">expressjs/express#5551</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/crandmck"><code>@​crandmck</code></a> made their first contribution in <a href="https://redirect.github.com/expressjs/express/pull/5541">expressjs/express#5541</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/expressjs/express/compare/4.18.3...4.19.0">https://github.com/expressjs/express/compare/4.18.3...4.19.0</a></p>
<h2>4.18.3</h2>
<h2>Main Changes</h2>
<ul>
<li>Fix routing requests without method</li>
<li>deps: body-parser@1.20.2
<ul>
<li>Fix strict json error message on Node.js 19+</li>
<li>deps: content-type@~1.0.5</li>
<li>deps: raw-body@2.5.2</li>
</ul>
</li>
</ul>
<h2>Other Changes</h2>
<ul>
<li>Use https: protocol instead of deprecated git: protocol by <a href="https://github.com/vcsjones"><code>@​vcsjones</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5032">expressjs/express#5032</a></li>
<li>build: Node.js@16.18 and Node.js@18.12 by <a href="https://github.com/abenhamdine"><code>@​abenhamdine</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5034">expressjs/express#5034</a></li>
<li>ci: update actions/checkout to v3 by <a href="https://github.com/armujahid"><code>@​armujahid</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5027">expressjs/express#5027</a></li>
<li>test: remove unused function arguments in params by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5124">expressjs/express#5124</a></li>
<li>Remove unused originalIndex from acceptParams by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5119">expressjs/express#5119</a></li>
<li>Fixed typos by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5117">expressjs/express#5117</a></li>
<li>examples: remove unused params by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5113">expressjs/express#5113</a></li>
<li>fix: parameter str is not described in JSDoc by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5130">expressjs/express#5130</a></li>
<li>fix: typos in History.md by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5131">expressjs/express#5131</a></li>
<li>build : add Node.js@19.7 by <a href="https://github.com/abenhamdine"><code>@​abenhamdine</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5028">expressjs/express#5028</a></li>
<li>test: remove unused function arguments in params by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5137">expressjs/express#5137</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/expressjs/express/blob/master/History.md">express's changelog</a>.</em></p>
<blockquote>
<h1>4.19.2 / 2024-03-25</h1>
<ul>
<li>Improved fix for open redirect allow list bypass</li>
</ul>
<h1>4.19.1 / 2024-03-20</h1>
<ul>
<li>Allow passing non-strings to res.location with new encoding handling checks</li>
</ul>
<h1>4.19.0 / 2024-03-20</h1>
<ul>
<li>Prevent open redirect allow list bypass due to encodeurl</li>
<li>deps: cookie@0.6.0</li>
</ul>
<h1>4.18.3 / 2024-02-29</h1>
<ul>
<li>Fix routing requests without method</li>
<li>deps: body-parser@1.20.2
<ul>
<li>Fix strict json error message on Node.js 19+</li>
<li>deps: content-type@~1.0.5</li>
<li>deps: raw-body@2.5.2</li>
</ul>
</li>
<li>deps: cookie@0.6.0
<ul>
<li>Add <code>partitioned</code> option</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="04bc62787b"><code>04bc627</code></a> 4.19.2</li>
<li><a href="da4d763ff6"><code>da4d763</code></a> Improved fix for open redirect allow list bypass</li>
<li><a href="4f0f6cc67d"><code>4f0f6cc</code></a> 4.19.1</li>
<li><a href="a003cfab03"><code>a003cfa</code></a> Allow passing non-strings to res.location with new encoding handling checks f...</li>
<li><a href="a1fa90fcea"><code>a1fa90f</code></a> fixed un-edited version in history.md for 4.19.0</li>
<li><a href="11f2b1db22"><code>11f2b1d</code></a> build: fix build due to inconsistent supertest behavior in older versions</li>
<li><a href="084e36506a"><code>084e365</code></a> 4.19.0</li>
<li><a href="0867302ddb"><code>0867302</code></a> Prevent open redirect allow list bypass due to encodeurl</li>
<li><a href="567c9c665d"><code>567c9c6</code></a> Add note on how to update docs for new release (<a href="https://redirect.github.com/expressjs/express/issues/5541">#5541</a>)</li>
<li><a href="69a4cf2819"><code>69a4cf2</code></a> deps: cookie@0.6.0</li>
<li>Additional commits viewable in <a href="https://github.com/expressjs/express/compare/4.18.2...4.19.2">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~wesleytodd">wesleytodd</a>, a new releaser for express since your current version.</p>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=express&package-manager=npm_and_yarn&previous-version=4.18.2&new-version=4.19.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/alcionai/corso/network/alerts).

</details>
2024-03-25 21:07:53 +00:00
ashmrtn
686867bd96
Pull in kopia with fixed manifest error wrap (#5273)
Updated version only contains the change to not clobber manifest compaction error messages.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-03-25 20:57:33 +00:00
ashmrtn
cd41d2fbce
Reduce test to just a single site to avoid failure (#5274)
#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-03-25 20:16:43 +00:00
dependabot[bot]
2b79c1b797
⬆️ Bump sass from 1.71.0 to 1.72.0 in /website (#5262)
Bumps [sass](https://github.com/sass/dart-sass) from 1.71.0 to 1.72.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.72.0</h2>
<p>To install Sass 1.72.0, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>
<p>Support adjacent <code>/</code>s without whitespace in between when parsing plain CSS expressions.</p>
</li>
<li>
<p>Allow the Node.js <code>pkg:</code> importer to load Sass stylesheets for <code>package.json</code> <code>exports</code> field entries without extensions.</p>
</li>
<li>
<p>When printing suggestions for variables, use underscores in variable names when the original usage used underscores.</p>
</li>
</ul>
<h3>JavaScript API</h3>
<ul>
<li>Properly resolve <code>pkg:</code> imports with the Node.js package importer when arguments are passed to the JavaScript process.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1720">full changelog</a> for changes in earlier releases.</p>
<h2>Dart Sass 1.71.1</h2>
<p>To install Sass 1.71.1, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<h3>Command-Line Interface</h3>
<ul>
<li>Ship the musl Linux release with the proper Dart executable.</li>
</ul>
<h3>JavaScript API</h3>
<ul>
<li>
<p>Export the <code>NodePackageImporter</code> class in ESM mode.</p>
</li>
<li>
<p>Allow <code>NodePackageImporter</code> to locate a default directory even when the entrypoint is an ESM module.</p>
</li>
</ul>
<h3>Dart API</h3>
<ul>
<li>Make passing a null argument to <code>NodePackageImporter()</code> a static error rather than just a runtime error.</li>
</ul>
<h3>Embedded Sass</h3>
<ul>
<li>In the JS Embedded Host, properly install the musl Linux embedded compiler when running on musl Linux.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1711">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.72.0</h2>
<ul>
<li>
<p>Support adjacent <code>/</code>s without whitespace in between when parsing plain CSS
expressions.</p>
</li>
<li>
<p>Allow the Node.js <code>pkg:</code> importer to load Sass stylesheets for <code>package.json</code>
<code>exports</code> field entries without extensions.</p>
</li>
<li>
<p>When printing suggestions for variables, use underscores in variable names
when the original usage used underscores.</p>
</li>
</ul>
<h3>JavaScript API</h3>
<ul>
<li>Properly resolve <code>pkg:</code> imports with the Node.js package importer when
arguments are passed to the JavaScript process.</li>
</ul>
<h2>1.71.1</h2>
<h3>Command-Line Interface</h3>
<ul>
<li>Ship the musl Linux release with the proper Dart executable.</li>
</ul>
<h3>JavaScript API</h3>
<ul>
<li>
<p>Export the <code>NodePackageImporter</code> class in ESM mode.</p>
</li>
<li>
<p>Allow <code>NodePackageImporter</code> to locate a default directory even when the
entrypoint is an ESM module.</p>
</li>
</ul>
<h3>Dart API</h3>
<ul>
<li>Make passing a null argument to <code>NodePackageImporter()</code> a static error rather
than just a runtime error.</li>
</ul>
<h3>Embedded Sass</h3>
<ul>
<li>In the JS Embedded Host, properly install the musl Linux embedded compiler
when running on musl Linux.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="ce16b35ca1"><code>ce16b35</code></a> Cut a release (<a href="https://redirect.github.com/sass/dart-sass/issues/2194">#2194</a>)</li>
<li><a href="9af6bbf8a0"><code>9af6bbf</code></a> Properly handle <code>pkg:</code> imports with args (<a href="https://redirect.github.com/sass/dart-sass/issues/2193">#2193</a>)</li>
<li><a href="033049102b"><code>0330491</code></a> Update to node 20 (<a href="https://redirect.github.com/sass/dart-sass/issues/2192">#2192</a>)</li>
<li><a href="48e2d0cb02"><code>48e2d0c</code></a> Preserve underscores in <code>VariableExpression.toString()</code> (<a href="https://redirect.github.com/sass/dart-sass/issues/2185">#2185</a>)</li>
<li><a href="6e2d637ac3"><code>6e2d637</code></a> Allow adjacent forward slashes in plain CSS expressions (<a href="https://redirect.github.com/sass/dart-sass/issues/2190">#2190</a>)</li>
<li><a href="fa4d909f92"><code>fa4d909</code></a> Bump softprops/action-gh-release from 1 to 2 (<a href="https://redirect.github.com/sass/dart-sass/issues/2191">#2191</a>)</li>
<li><a href="fd67fe678c"><code>fd67fe6</code></a> [Hotfix Node Package Importer]- Handle subpath without extensions (<a href="https://redirect.github.com/sass/dart-sass/issues/2184">#2184</a>)</li>
<li><a href="1b4d703ad3"><code>1b4d703</code></a> Release 1.71.1 (<a href="https://redirect.github.com/sass/dart-sass/issues/2182">#2182</a>)</li>
<li><a href="6d66c4376a"><code>6d66c43</code></a> Properly handle <code>new NodePackageImporter()</code> with an ESM entrypoint (<a href="https://redirect.github.com/sass/dart-sass/issues/2181">#2181</a>)</li>
<li><a href="85a932f648"><code>85a932f</code></a> Add missing ESM export of NodePackageImporter (<a href="https://redirect.github.com/sass/dart-sass/issues/2177">#2177</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/sass/dart-sass/compare/1.71.0...1.72.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.71.0&new-version=1.72.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-03-14 05:44:43 +00:00
dependabot[bot]
2ab6d34538
⬆️ Bump mermaid from 10.8.0 to 10.9.0 in /website (#5256)
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 10.8.0 to 10.9.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/mermaid-js/mermaid/releases">mermaid's releases</a>.</em></p>
<blockquote>
<h2>v10.9.0</h2>
<h1>Release Notes</h1>
<p>We now have Katex support!</p>
<p><img src="https://user-images.githubusercontent.com/16135852/161395052-4dee19da-faa6-4934-94f3-dd0cbcecfccd.png" alt="image" />
<a href="https://mermaid.live/edit#pako:eNptUl1r6zAM_SvClJHQ5mFh7I7CBuu2tz3tPl7dDsdxUkOTdI46Mlz998lJCh3ML5KOdc7xh4IyXWnVWtVeH3bw-oYtyHr8h2qxqBJEb_d6CAOncA-IrqX3kMVY0Rdv50Q2dppCxdI_uBRxZbchF_TgwEHEYGBBy5guFqj-Q5Y9nKIF4sbVtfCKKUg1FYmEymsTskJ0GsT-w1Motnl2ow1zyDWnY2M6kqZ8Luo0mpxgk0SL6-WsJIfK8ngoZgg_0Jtf0dtf0btL1JQd9SwrGqbz211crvu0vhCmDXpZLA1vAyLZgULbkZ1YJ3hKpubDTvc2ZH_u5F2N8-ZSdBNF4XnsHMavKGzt2mCE0jNouJqFXQUMhewjgvmJloLatpw5F-JPo_jLJJ5Qem_er88GRaPJu4Hl003XB2K5dS848WiRn-FZ-9weB4AmD7VSjfWNdqWMWYiOqGhnG4tqLWlpK33cEypsWVr1kbq_X61Ra_JHu1LHQ6nJPjstA9qodaX3veVvObPoTQ">Demo</a></p>
<h2>🚀 Features</h2>
<ul>
<li>Bump <code>@​zenuml/core</code> and update render options in mermaid-zenuml (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5257">#5257</a>) <a href="https://github.com/dontry"><code>@​dontry</code></a></li>
<li>Implement &quot;until&quot; keyword in gantt charts (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5224">#5224</a>) <a href="https://github.com/fzag"><code>@​fzag</code></a></li>
<li>Integrated Katex typesetting into flowcharts (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/2885">#2885</a>) <a href="https://github.com/NicolasNewman"><code>@​NicolasNewman</code></a></li>
</ul>
<h2>🧰 Maintenance</h2>
<ul>
<li>Add gitgraph parallel commits to docs (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5336">#5336</a>) <a href="https://github.com/NicolasCwy"><code>@​NicolasCwy</code></a></li>
<li>Update recommended Vitest extension (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5322">#5322</a>) <a href="https://github.com/NicolasCwy"><code>@​NicolasCwy</code></a></li>
<li>Correcting path to docker-entrypoint.sh (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5327">#5327</a>) <a href="https://github.com/bstordrup"><code>@​bstordrup</code></a></li>
<li>Fix chrome webstore url causing 404 (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5352">#5352</a>) <a href="https://github.com/Abrifq"><code>@​Abrifq</code></a></li>
<li>Fix color and arrow for merge commit (gitGraph) (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5152">#5152</a>) <a href="https://github.com/macherel"><code>@​macherel</code></a></li>
<li>Fix link to Contributors section in README (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5298">#5298</a>) <a href="https://github.com/BaumiCoder"><code>@​BaumiCoder</code></a></li>
<li>Fix macOS onboarding issues (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5262">#5262</a>) <a href="https://github.com/thedustin"><code>@​thedustin</code></a></li>
<li>Fix netlify deploy (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5332">#5332</a>) <a href="https://github.com/sidharthv96"><code>@​sidharthv96</code></a></li>
<li>Link to webhelp (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5316">#5316</a>) <a href="https://github.com/BaumiCoder"><code>@​BaumiCoder</code></a></li>
<li>Update contribute (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5268">#5268</a>) <a href="https://github.com/FutzMonitor"><code>@​FutzMonitor</code></a></li>
<li>Updates Timeline Documentation (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5315">#5315</a>) <a href="https://github.com/FutzMonitor"><code>@​FutzMonitor</code></a></li>
<li>[Docs] Updated chrome extension url's to new store (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5297">#5297</a>) <a href="https://github.com/Abrifq"><code>@​Abrifq</code></a></li>
<li>chore: Update CSpell configuration (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5286">#5286</a>) <a href="https://github.com/Jason3S"><code>@​Jason3S</code></a></li>
<li>feat: add name field to the actors (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5284">#5284</a>) <a href="https://github.com/ad1992"><code>@​ad1992</code></a></li>
<li>fix typo cutomers =&gt; customers (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5269">#5269</a>) <a href="https://github.com/elgalu"><code>@​elgalu</code></a></li>
</ul>
<h2>📚 Documentation</h2>
<ul>
<li>Add new extension to integrations (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5287">#5287</a>) <a href="https://github.com/BoDonkey"><code>@​BoDonkey</code></a></li>
<li>Added link to Blazorade Mermaid to the community integrations page. (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5333">#5333</a>) <a href="https://github.com/MikaBerglund"><code>@​MikaBerglund</code></a></li>
<li>Replace version number placeholder (<a href="https://redirect.github.com/mermaid-js/mermaid/issues/5304">#5304</a>) <a href="https://github.com/BaumiCoder"><code>@​BaumiCoder</code></a></li>
</ul>
<p>🎉 <strong>Thanks to all contributors helping with this release!</strong> 🎉</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="da33867ad7"><code>da33867</code></a> Draft release</li>
<li><a href="539010c65c"><code>539010c</code></a> Merge pull request <a href="https://redirect.github.com/mermaid-js/mermaid/issues/5337">#5337</a> from mermaid-js/release/10.9.0</li>
<li><a href="cbe44a6cff"><code>cbe44a6</code></a> v10.9.0</li>
<li><a href="b077fedd4c"><code>b077fed</code></a> Merge branch 'develop' into release/10.9.0</li>
<li><a href="5aa884f594"><code>5aa884f</code></a> Merge pull request <a href="https://redirect.github.com/mermaid-js/mermaid/issues/5354">#5354</a> from mermaid-js/renovate/patch-all-patch</li>
<li><a href="5b3f320e5d"><code>5b3f320</code></a> Merge branch 'develop' into renovate/patch-all-patch</li>
<li><a href="803e068630"><code>803e068</code></a> Merge branch 'master' into develop</li>
<li><a href="3147bb34ee"><code>3147bb3</code></a> Update docs</li>
<li><a href="8daa28dd8b"><code>8daa28d</code></a> Lychee ignore chrome webstore</li>
<li><a href="231534a0db"><code>231534a</code></a> Update link</li>
<li>Additional commits viewable in <a href="https://github.com/mermaid-js/mermaid/compare/v10.8.0...v10.9.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mermaid&package-manager=npm_and_yarn&previous-version=10.8.0&new-version=10.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-03-06 05:45:41 +00:00
Abin Simon
e0884c734c
Fix ics high memory usage by disabling auto wrap text in html2text (#5244)
Related: jaytaylor/html2text#48<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [x]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-23 06:20:59 +00:00
ashmrtn
f3fdb4a885
harden sanitree population (#5237)
Allow sanity tree checking to require multiple folder subtrees have no
errors. This allows us to ensure both the source folder subtree and
restore folder subtree are populated without issue.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [x] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-21 23:53:12 +00:00
ashmrtn
f4dbaf60b0
Normalize case when checking for user email (#5240)
Reduce chance of test failures by normalizing the case prior to comparison. This should hopefully reduce spurious test failures.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-20 22:45:21 +00:00
Niraj Tolia
b9b5650506
Tweak website (#5238)
#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🗺️ Documentation
2024-02-19 04:56:16 +00:00
Abhishek Pandey
f28e79c098
Log token lifetimes on 401 errors (#5239)
<!-- PR description-->

* When we encounter 401s, process the JWT token present in the [`Authorization` header](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#use-a-token) of request. 
* Dump the issued at time and expires at time for the token. 
---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2024-02-18 00:20:55 +00:00
Abin Simon
42af271526
Close body of file after writing to zip file in export (#5234)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-17 17:35:18 +00:00
dependabot[bot]
d87435fdc2
⬆️ Bump sass from 1.70.0 to 1.71.0 in /website (#5235)
Bumps [sass](https://github.com/sass/dart-sass) from 1.70.0 to 1.71.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/releases">sass's releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.71.0</h2>
<p>To install Sass 1.71.0, download one of the packages below and <a href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or see <a href="https://sass-lang.com/install">the Sass website</a> for full installation instructions.</p>
<h1>Changes</h1>
<p>For more information about <code>pkg:</code> importers, see <a href="https://sass-lang.com/blog/announcing-pkg-importers">the announcement</a> on the Sass blog.</p>
<h3>Command-Line Interface</h3>
<ul>
<li>Add a <code>--pkg-importer</code> flag to enable built-in <code>pkg:</code> importers. Currently this only supports the Node.js package resolution algorithm, via <code>--pkg-importer=node</code>. For example, <code>@use &quot;pkg:bootstrap&quot;</code> will load <code>node_modules/bootstrap/scss/bootstrap.scss</code>.</li>
</ul>
<h3>JavaScript API</h3>
<ul>
<li>Add a <code>NodePackageImporter</code> importer that can be passed to the <code>importers</code> option. This loads files using the <code>pkg:</code> URL scheme according to the Node.js package resolution algorithm. For example, <code>@use &quot;pkg:bootstrap&quot;</code> will load <code>node_modules/bootstrap/scss/bootstrap.scss</code>. The constructor takes a single optional argument, which indicates the base directory to use when locating <code>node_modules</code> directories. It defaults to <code>path.dirname(require.main.filename)</code>.</li>
</ul>
<h3>Dart API</h3>
<ul>
<li>Add a <code>NodePackageImporter</code> importer that can be passed to the <code>importers</code> option. This loads files using the <code>pkg:</code> URL scheme according to the Node.js package resolution algorithm. For example, <code>@use &quot;pkg:bootstrap&quot;</code> will load <code>node_modules/bootstrap/scss/bootstrap.scss</code>. The constructor takes a single argument, which indicates the base directory to use when locating <code>node_modules</code> directories.</li>
</ul>
<p>See the <a href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#1710">full changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's changelog</a>.</em></p>
<blockquote>
<h2>1.71.0</h2>
<p>For more information about <code>pkg:</code> importers, see <a href="https://sass-lang.com/blog/announcing-pkg-importers">the
announcement</a> on the Sass blog.</p>
<h3>Command-Line Interface</h3>
<ul>
<li>Add a <code>--pkg-importer</code> flag to enable built-in <code>pkg:</code> importers. Currently
this only supports the Node.js package resolution algorithm, via
<code>--pkg-importer=node</code>. For example, <code>@use &quot;pkg:bootstrap&quot;</code> will load
<code>node_modules/bootstrap/scss/bootstrap.scss</code>.</li>
</ul>
<h3>JavaScript API</h3>
<ul>
<li>Add a <code>NodePackageImporter</code> importer that can be passed to the <code>importers</code>
option. This loads files using the <code>pkg:</code> URL scheme according to the Node.js
package resolution algorithm. For example, <code>@use &quot;pkg:bootstrap&quot;</code> will load
<code>node_modules/bootstrap/scss/bootstrap.scss</code>. The constructor takes a single
optional argument, which indicates the base directory to use when locating
<code>node_modules</code> directories. It defaults to
<code>path.dirname(require.main.filename)</code>.</li>
</ul>
<h3>Dart API</h3>
<ul>
<li>Add a <code>NodePackageImporter</code> importer that can be passed to the <code>importers</code>
option. This loads files using the <code>pkg:</code> URL scheme according to the Node.js
package resolution algorithm. For example, <code>@use &quot;pkg:bootstrap&quot;</code> will load
<code>node_modules/bootstrap/scss/bootstrap.scss</code>. The constructor takes a single
argument, which indicates the base directory to use when locating
<code>node_modules</code> directories.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="3e6721e79f"><code>3e6721e</code></a> Fix new static warnings with Dart 3.3 (<a href="https://redirect.github.com/sass/dart-sass/issues/2173">#2173</a>)</li>
<li><a href="2cab33e2b3"><code>2cab33e</code></a> Update the language revision in Homebrew on release (<a href="https://redirect.github.com/sass/dart-sass/issues/2171">#2171</a>)</li>
<li><a href="84ededd368"><code>84ededd</code></a> Use musl support in cli_pkg (<a href="https://redirect.github.com/sass/dart-sass/issues/2172">#2172</a>)</li>
<li><a href="00571ec531"><code>00571ec</code></a> Add a <code>--pkg-importer</code> flag (<a href="https://redirect.github.com/sass/dart-sass/issues/2169">#2169</a>)</li>
<li><a href="84f31f0def"><code>84f31f0</code></a> Update pubspec/changelog for <code>pkg:</code> importers (<a href="https://redirect.github.com/sass/dart-sass/issues/2168">#2168</a>)</li>
<li><a href="9ee5408211"><code>9ee5408</code></a> [Package Importer] Dart Implementation (<a href="https://redirect.github.com/sass/dart-sass/issues/2130">#2130</a>)</li>
<li><a href="9423aa53ae"><code>9423aa5</code></a> Use macos-14 runner instead of macos-latest-xlarge runner (<a href="https://redirect.github.com/sass/dart-sass/issues/2167">#2167</a>)</li>
<li><a href="bbf97b4fb4"><code>bbf97b4</code></a> Remove the sass dependency from package.json (<a href="https://redirect.github.com/sass/dart-sass/issues/2162">#2162</a>)</li>
<li>See full diff in <a href="https://github.com/sass/dart-sass/compare/1.70.0...1.71.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.70.0&new-version=1.71.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-02-16 05:34:00 +00:00
Keepers
8bdf86bbad
apply missing opts to api client (#5233)
#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🐛 Bugfix

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
2024-02-15 17:25:49 +00:00
Abin Simon
bf52fdbe6a
Log every 1000 items when exporting (#5227)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-15 06:45:16 +00:00
ashmrtn
90d6db486b
Log if the context expired during retry (#5229)
#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-15 01:24:42 +00:00
Keepers
f10730cf98
add new control opt for skipping event 503s (#5223)
adds a new control option for skipping certain event item 503 failures.
Also adds a skip cause for that case. And exports the skipCause value
for future preparation.
2024-02-14 16:55:52 -07:00
Keepers
bb2bd6df3f
add authentication to requester (#5198)
the graph requester for large item downloads now includes the option to authenticate requests.  The option is configured at the time of creating the requester, therefore all requests using that servier are either authenticatd or not. In our case, we're opting to authenticate all requests, since we do not use this requester for non-graph api calls, and even if we did the addition of auth headers is likely benign.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Test Plan

- [x] 💚 E2E
2024-02-14 17:50:36 +00:00
ashmrtn
5e8407a970
Don't alert on old compressor (#5222)
When verifying the repo config, don't create an alert if the repo has the old s2-default compressor that we temporarily used.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2024-02-13 21:18:29 +00:00
Hitesh Pattanayak
4b56754546
sanitizes replyTo emailAddresses (#5221)
sanitizes replyTo emailAddresses based on:
- valid email address format
- valid DN format

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [x] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)
INC-43

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
2024-02-13 18:52:11 +00:00
ashmrtn
28aba60cc5
Check for and retry 404s with no content (#5217)
We've started seeing 404 errors with no content being returned. Check for these in the http wrapper we use and retry them.

While graph SDK returns an error for this sort of situation it's a very basic error since it normally expects to parse info out of the response body. Therefore it should be safe to inject our own error that we can check for.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2024-02-13 02:26:04 +00:00
ashmrtn
03048a6ca8
Fix contains check for exchange nightly tests (#5214)
Data being checked drifted due to recent changes in the test helper
and possible CLI output changes

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-12 20:32:25 +00:00
dependabot[bot]
97535e2afc
⬆️ Bump golangci/golangci-lint-action from 3 to 4 (#5213)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/golangci/golangci-lint-action/releases">golangci/golangci-lint-action's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>Documentation</h3>
<ul>
<li>docs: update examples by <a href="https://github.com/KunalSin9h"><code>@​KunalSin9h</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/826">golangci/golangci-lint-action#826</a></li>
<li>docs: update section about GitHub Annotations by <a href="https://github.com/JustinDFuller"><code>@​JustinDFuller</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/931">golangci/golangci-lint-action#931</a></li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.3.0 to 6.4.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/829">golangci/golangci-lint-action#829</a></li>
<li>build(deps-dev): bump eslint-plugin-import from 2.28.0 to 2.28.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/830">golangci/golangci-lint-action#830</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.5.0 to 20.5.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/827">golangci/golangci-lint-action#827</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.3.0 to 6.4.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/831">golangci/golangci-lint-action#831</a></li>
<li>build(deps-dev): bump prettier from 3.0.1 to 3.0.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/828">golangci/golangci-lint-action#828</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.5.1 to 20.5.7 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/833">golangci/golangci-lint-action#833</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.4.0 to 6.4.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/834">golangci/golangci-lint-action#834</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.4.0 to 6.4.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/835">golangci/golangci-lint-action#835</a></li>
<li>build(deps-dev): bump eslint from 8.47.0 to 8.48.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/837">golangci/golangci-lint-action#837</a></li>
<li>build(deps-dev): bump typescript from 5.1.6 to 5.2.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/836">golangci/golangci-lint-action#836</a></li>
<li>build(deps): bump <code>@​types/semver</code> from 7.5.0 to 7.5.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/838">golangci/golangci-lint-action#838</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.4.1 to 6.5.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/839">golangci/golangci-lint-action#839</a></li>
<li>build(deps-dev): bump prettier from 3.0.2 to 3.0.3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/842">golangci/golangci-lint-action#842</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.4.1 to 6.5.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/840">golangci/golangci-lint-action#840</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.5.7 to 20.5.9 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/841">golangci/golangci-lint-action#841</a></li>
<li>chore: bump to use node20 runtime, actions/checkout to v4 by <a href="https://github.com/chenrui333"><code>@​chenrui333</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/843">golangci/golangci-lint-action#843</a></li>
<li>build(deps): bump actions/checkout from 3 to 4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/845">golangci/golangci-lint-action#845</a></li>
<li>build(deps-dev): bump eslint from 8.48.0 to 8.49.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/846">golangci/golangci-lint-action#846</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.5.9 to 20.6.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/847">golangci/golangci-lint-action#847</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.5.0 to 6.6.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/848">golangci/golangci-lint-action#848</a></li>
<li>build(deps-dev): bump <code>@​vercel/ncc</code> from 0.36.1 to 0.38.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/850">golangci/golangci-lint-action#850</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.5.0 to 6.6.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/849">golangci/golangci-lint-action#849</a></li>
<li>build(deps): bump <code>@​types/semver</code> from 7.5.1 to 7.5.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/853">golangci/golangci-lint-action#853</a></li>
<li>build(deps): bump <code>@​types/tmp</code> from 0.2.3 to 0.2.4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/854">golangci/golangci-lint-action#854</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.6.0 to 6.7.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/855">golangci/golangci-lint-action#855</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.6.0 to 20.6.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/857">golangci/golangci-lint-action#857</a></li>
<li>build(deps): bump <code>@​actions/core</code> from 1.10.0 to 1.10.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/856">golangci/golangci-lint-action#856</a></li>
<li>build(deps-dev): bump eslint from 8.49.0 to 8.50.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/859">golangci/golangci-lint-action#859</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.6.2 to 20.6.5 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/860">golangci/golangci-lint-action#860</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.6.0 to 6.7.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/861">golangci/golangci-lint-action#861</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.7.0 to 6.7.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/862">golangci/golangci-lint-action#862</a></li>
<li>build(deps): bump <code>@​types/semver</code> from 7.5.2 to 7.5.3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/864">golangci/golangci-lint-action#864</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.7.2 to 6.7.3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/865">golangci/golangci-lint-action#865</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.6.5 to 20.8.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/867">golangci/golangci-lint-action#867</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.7.2 to 6.7.3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/866">golangci/golangci-lint-action#866</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.7.3 to 6.7.4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/868">golangci/golangci-lint-action#868</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.8.0 to 20.8.3 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/869">golangci/golangci-lint-action#869</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.7.3 to 6.7.4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/870">golangci/golangci-lint-action#870</a></li>
<li>build(deps-dev): bump eslint from 8.50.0 to 8.51.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/871">golangci/golangci-lint-action#871</a></li>
<li>build(deps): bump <code>@​actions/http-client</code> from 2.1.1 to 2.2.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/872">golangci/golangci-lint-action#872</a></li>
<li>build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.7.4 to 6.7.5 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/874">golangci/golangci-lint-action#874</a></li>
<li>build(deps): bump <code>@​types/node</code> from 20.8.3 to 20.8.6 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/golangci/golangci-lint-action/pull/875">golangci/golangci-lint-action#875</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="3cfe3a4abb"><code>3cfe3a4</code></a> build(deps): bump <code>@​actions/cache</code> from 3.2.3 to 3.2.4 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/963">#963</a>)</li>
<li><a href="cbc59cf0d1"><code>cbc59cf</code></a> build(deps-dev): bump prettier from 3.2.4 to 3.2.5 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/960">#960</a>)</li>
<li><a href="459a04b021"><code>459a04b</code></a> build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.19.1 to 6.20.0 ...</li>
<li><a href="e2315b67db"><code>e2315b6</code></a> build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.19.1 to 6.20.0 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/961">#961</a>)</li>
<li><a href="d6173a45d0"><code>d6173a4</code></a> build(deps): bump <code>@​types/node</code> from 20.11.10 to 20.11.16 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/962">#962</a>)</li>
<li><a href="0e8f5bf773"><code>0e8f5bf</code></a> build(deps): bump <code>@​types/node</code> from 20.11.5 to 20.11.10 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/958">#958</a>)</li>
<li><a href="349d20632d"><code>349d206</code></a> build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.19.0 to 6.19.1 ...</li>
<li><a href="2221aee284"><code>2221aee</code></a> build(deps-dev): bump <code>@​typescript-eslint/parser</code> from 6.18.1 to 6.19.1 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/954">#954</a>)</li>
<li><a href="3b44ae5b24"><code>3b44ae5</code></a> build(deps-dev): bump <code>@​typescript-eslint/eslint-plugin</code> from 6.18.1 to 6.19.0 ...</li>
<li><a href="323b871bbc"><code>323b871</code></a> build(deps-dev): bump prettier from 3.2.2 to 3.2.4 (<a href="https://redirect.github.com/golangci/golangci-lint-action/issues/950">#950</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/golangci/golangci-lint-action/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-02-12 19:53:24 +00:00
dependabot[bot]
cd7450395e
⬆️ Bump github.com/minio/minio-go/v7 from 7.0.66 to 7.0.67 in /src (#5210)
Bumps [github.com/minio/minio-go/v7](https://github.com/minio/minio-go) from 7.0.66 to 7.0.67.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/minio/minio-go/releases">github.com/minio/minio-go/v7's releases</a>.</em></p>
<blockquote>
<h2>Bugfix release</h2>
<h2>What's Changed</h2>
<ul>
<li>detect offline for more valid errors by <a href="https://github.com/harshavardhana"><code>@​harshavardhana</code></a> in <a href="https://redirect.github.com/minio/minio-go/pull/1919">minio/minio-go#1919</a></li>
<li>NEW API: GetObjectAttributes by <a href="https://github.com/zveinn"><code>@​zveinn</code></a> in <a href="https://redirect.github.com/minio/minio-go/pull/1921">minio/minio-go#1921</a></li>
<li>fix: support more type to StringSet umnarshaJSON by <a href="https://github.com/jiuker"><code>@​jiuker</code></a> in <a href="https://redirect.github.com/minio/minio-go/pull/1925">minio/minio-go#1925</a></li>
<li>Update api-remove.go by <a href="https://github.com/fwessels"><code>@​fwessels</code></a> in <a href="https://redirect.github.com/minio/minio-go/pull/1926">minio/minio-go#1926</a></li>
<li>Enable --expired-object-all-versions by <a href="https://github.com/shtripat"><code>@​shtripat</code></a> in <a href="https://redirect.github.com/minio/minio-go/pull/1927">minio/minio-go#1927</a></li>
<li>fix: latest linter issues by <a href="https://github.com/harshavardhana"><code>@​harshavardhana</code></a> in <a href="https://redirect.github.com/minio/minio-go/pull/1929">minio/minio-go#1929</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/zveinn"><code>@​zveinn</code></a> made their first contribution in <a href="https://redirect.github.com/minio/minio-go/pull/1921">minio/minio-go#1921</a></li>
<li><a href="https://github.com/jiuker"><code>@​jiuker</code></a> made their first contribution in <a href="https://redirect.github.com/minio/minio-go/pull/1925">minio/minio-go#1925</a></li>
<li><a href="https://github.com/shtripat"><code>@​shtripat</code></a> made their first contribution in <a href="https://redirect.github.com/minio/minio-go/pull/1927">minio/minio-go#1927</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/minio/minio-go/compare/v7.0.66...v7.0.67">https://github.com/minio/minio-go/compare/v7.0.66...v7.0.67</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="99c6311121"><code>99c6311</code></a> fix: latest linter issues (<a href="https://redirect.github.com/minio/minio-go/issues/1929">#1929</a>)</li>
<li><a href="72b90536ab"><code>72b9053</code></a> Enable --expired-object-all-versions (<a href="https://redirect.github.com/minio/minio-go/issues/1927">#1927</a>)</li>
<li><a href="c6d47d8f4b"><code>c6d47d8</code></a> Update api-remove.go (<a href="https://redirect.github.com/minio/minio-go/issues/1926">#1926</a>)</li>
<li><a href="6ad2b4a178"><code>6ad2b4a</code></a> fix: support all types in StringSet JSON unmarshal (<a href="https://redirect.github.com/minio/minio-go/issues/1925">#1925</a>)</li>
<li><a href="76a41461fe"><code>76a4146</code></a> NEW API: GetObjectAttributes (<a href="https://redirect.github.com/minio/minio-go/issues/1921">#1921</a>)</li>
<li><a href="56d9949682"><code>56d9949</code></a> detect offline for more valid errors (<a href="https://redirect.github.com/minio/minio-go/issues/1919">#1919</a>)</li>
<li><a href="f86f90f5f4"><code>f86f90f</code></a> update x/crypto and add CREDITS</li>
<li><a href="594eb81116"><code>594eb81</code></a> Update version to next release</li>
<li>See full diff in <a href="https://github.com/minio/minio-go/compare/v7.0.66...v7.0.67">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/minio/minio-go/v7&package-manager=go_modules&previous-version=7.0.66&new-version=7.0.67)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2024-02-12 18:16:07 +00:00
Abhishek Pandey
411ef24024
Handle item attachments with missing name (#5209)
<!-- PR description-->

Extending the earlier fix in https://github.com/alcionai/corso/pull/5199 to `itemAttachments`. Posts are not impacted here since they don't have attachment types like messages do.

---

#### Does this PR need a docs update or release note?

- [x]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2024-02-11 22:13:20 +00:00
Vaibhav Kamra
b3b52c0dfc
Do not backup shared calendars (#5207)
Skip backup of shared calendars. These will be backed up with the resource that owns the calendar.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [x] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-11 18:58:09 +00:00
Abin Simon
8502e1fee6
Use recurrence timezone for ics exports (#5206)
<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [x]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2024-02-10 08:50:38 +00:00
Abin Simon
f0b8041c3f
Fix possible panic in contacts fetch (#5205)
We were not checking for the error returned by the Get method before trying to use the result to get contact info.

<!-- PR description-->

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2024-02-10 07:57:57 +00:00
Abhishek Pandey
f92f811559
Retry more errors in graph adapter wrapper (#5203)
<!-- PR description-->

* We have started seeing `io.ErrUnexpectedEOF` and `read: connection timed out` in the last 2 days for exchange backups. Retry those.
* Also increase retry count from 3 to 6. This is more of a hail mary to retry `InvalidAuthenticationToken` errors. We have observed that retries do help. But for a small set of requests, we end up exhausting retries and eventually fail with InvalidAuthenticationToken error. Hoping that bumping this to 6 will get us some relief. This fix may be removed if we find the rootcause/pattern behind this.
* The event list test was taking > 150 secs. Thought I'd push this change as its a small fix. I first thought my PR https://github.com/alcionai/corso/pull/5202 broke the test. So I investigated this.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #<issue>

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2024-02-10 05:11:05 +00:00
97 changed files with 8225 additions and 723 deletions

View File

@ -1,4 +1,5 @@
name: Backup Restore Test name: Backup Restore Test
description: Run various backup/restore/export tests for a service.
inputs: inputs:
service: service:

View File

@ -1,4 +1,5 @@
name: Setup and Cache Golang name: Setup and Cache Golang
description: Build golang binaries for later use in CI.
# clone of: https://github.com/magnetikonline/action-golang-cache/blob/main/action.yaml # clone of: https://github.com/magnetikonline/action-golang-cache/blob/main/action.yaml
# #

View File

@ -1,4 +1,5 @@
name: Publish Binary name: Publish Binary
description: Publish binary artifacts.
inputs: inputs:
version: version:

View File

@ -1,4 +1,5 @@
name: Publish Website name: Publish Website
description: Publish website artifacts.
inputs: inputs:
aws-iam-role: aws-iam-role:

View File

@ -1,4 +1,5 @@
name: Purge M365 User Data name: Purge M365 User Data
description: Deletes M365 data generated during CI tests.
# Hard deletion of an m365 user's data. Our CI processes create a lot # Hard deletion of an m365 user's data. Our CI processes create a lot
# of data churn (creation and immediate deletion) of files, the likes # of data churn (creation and immediate deletion) of files, the likes
@ -30,12 +31,19 @@ inputs:
description: Secret value of for AZURE_CLIENT_ID description: Secret value of for AZURE_CLIENT_ID
azure-client-secret: azure-client-secret:
description: Secret value of for AZURE_CLIENT_SECRET description: Secret value of for AZURE_CLIENT_SECRET
azure-pnp-client-id:
description: Secret value of AZURE_PNP_CLIENT_ID
azure-pnp-client-cert:
description: Base64 encoded private certificate for the azure-pnp-client-id (Secret value of AZURE_PNP_CLIENT_CERT)
azure-tenant-id: azure-tenant-id:
description: Secret value of for AZURE_TENANT_ID description: Secret value of AZURE_TENANT_ID
m365-admin-user: m365-admin-user:
description: Secret value of for M365_TENANT_ADMIN_USER description: Secret value of for M365_TENANT_ADMIN_USER
m365-admin-password: m365-admin-password:
description: Secret value of for M365_TENANT_ADMIN_PASSWORD description: Secret value of for M365_TENANT_ADMIN_PASSWORD
tenant-domain:
description: The domain of the tenant (ex. 10rqc2.onmicrosft.com)
required: true
runs: runs:
using: composite using: composite
@ -53,7 +61,13 @@ runs:
AZURE_CLIENT_ID: ${{ inputs.azure-client-id }} AZURE_CLIENT_ID: ${{ inputs.azure-client-id }}
AZURE_CLIENT_SECRET: ${{ inputs.azure-client-secret }} AZURE_CLIENT_SECRET: ${{ inputs.azure-client-secret }}
AZURE_TENANT_ID: ${{ inputs.azure-tenant-id }} AZURE_TENANT_ID: ${{ inputs.azure-tenant-id }}
run: ./exchangePurge.ps1 -User ${{ inputs.user }} -FolderNamePurgeList PersonMetadata -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }} run: |
for ($ATTEMPT_NUM = 1; $ATTEMPT_NUM -le 3; $ATTEMPT_NUM++)
{
if (./exchangePurge.ps1 -User ${{ inputs.user }} -FolderNamePurgeList PersonMetadata -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }}) {
break
}
}
# TODO(ashmrtn): Re-enable when we figure out errors we're seeing with Get-Mailbox call. # TODO(ashmrtn): Re-enable when we figure out errors we're seeing with Get-Mailbox call.
#- name: Reset retention for all mailboxes to 0 #- name: Reset retention for all mailboxes to 0
@ -74,10 +88,16 @@ runs:
shell: pwsh shell: pwsh
working-directory: ./src/cmd/purge/scripts working-directory: ./src/cmd/purge/scripts
env: env:
M365_TENANT_ADMIN_USER: ${{ inputs.m365-admin-user }} AZURE_CLIENT_ID: ${{ inputs.azure-pnp-client-id }}
M365_TENANT_ADMIN_PASSWORD: ${{ inputs.m365-admin-password }} AZURE_APP_CERT: ${{ inputs.azure-pnp-client-cert }}
TENANT_DOMAIN: ${{ inputs.tenant-domain }}
run: | run: |
./onedrivePurge.ps1 -User ${{ inputs.user }} -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }} for ($ATTEMPT_NUM = 1; $ATTEMPT_NUM -le 3; $ATTEMPT_NUM++)
{
if (./onedrivePurge.ps1 -User ${{ inputs.user }} -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }}) {
break
}
}
################################################################################################################ ################################################################################################################
# Sharepoint # Sharepoint
@ -88,6 +108,14 @@ runs:
shell: pwsh shell: pwsh
working-directory: ./src/cmd/purge/scripts working-directory: ./src/cmd/purge/scripts
env: env:
M365_TENANT_ADMIN_USER: ${{ inputs.m365-admin-user }} AZURE_CLIENT_ID: ${{ inputs.azure-pnp-client-id }}
M365_TENANT_ADMIN_PASSWORD: ${{ inputs.m365-admin-password }} AZURE_APP_CERT: ${{ inputs.azure-pnp-client-cert }}
run: ./onedrivePurge.ps1 -Site ${{ inputs.site }} -LibraryNameList "${{ inputs.libraries }}".split(",") -FolderPrefixPurgeList ${{ inputs.folder-prefix }} -LibraryPrefixDeleteList ${{ inputs.library-prefix && inputs.library-prefix || '[]' }} -PurgeBeforeTimestamp ${{ inputs.older-than }} TENANT_DOMAIN: ${{ inputs.tenant-domain }}
run: |
for ($ATTEMPT_NUM = 1; $ATTEMPT_NUM -le 3; $ATTEMPT_NUM++)
{
if (./onedrivePurge.ps1 -Site ${{ inputs.site }} -LibraryNameList "${{ inputs.libraries }}".split(",") -FolderPrefixPurgeList ${{ inputs.folder-prefix }} -LibraryPrefixDeleteList ${{ inputs.library-prefix && inputs.library-prefix || '[]' }} -PurgeBeforeTimestamp ${{ inputs.older-than }}) {
break
}
}

View File

@ -1,4 +1,5 @@
name: Send a message to Teams name: Send a message to Teams
description: Send messages to communication apps.
inputs: inputs:
msg: msg:

View File

@ -1,4 +1,5 @@
name: Lint Website name: Lint Website
description: Lint website content.
inputs: inputs:
version: version:

View File

@ -40,5 +40,5 @@ jobs:
if: failure() if: failure()
uses: ./.github/actions/teams-message uses: ./.github/actions/teams-message
with: with:
msg: "[FAILED] Publishing Binary" msg: "[CORSO FAILED] Publishing Binary"
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }} teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}

View File

@ -463,7 +463,7 @@ jobs:
go-version-file: src/go.mod go-version-file: src/go.mod
- name: Go Lint - name: Go Lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v4
with: with:
# Keep pinned to a verson as sometimes updates will add new lint # Keep pinned to a verson as sometimes updates will add new lint
# failures in unchanged code. # failures in unchanged code.

View File

@ -12,7 +12,7 @@ jobs:
continue-on-error: true continue-on-error: true
strategy: strategy:
matrix: matrix:
user: [ CORSO_M365_TEST_USER_ID, CORSO_SECONDARY_M365_TEST_USER_ID, '' ] user: [CORSO_M365_TEST_USER_ID, CORSO_SECONDARY_M365_TEST_USER_ID, ""]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -33,12 +33,15 @@ jobs:
azure-tenant-id: ${{ secrets.TENANT_ID }} azure-tenant-id: ${{ secrets.TENANT_ID }}
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }} m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }} m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
tenant-domain: ${{ vars.TENANT_DOMAIN }}
- name: Notify failure in teams - name: Notify failure in teams
if: failure() if: failure()
uses: ./.github/actions/teams-message uses: ./.github/actions/teams-message
with: with:
msg: "[FAILED] ${{ vars[matrix.user] }} CI Cleanup" msg: "[CORSO FAILED] ${{ vars[matrix.user] }} CI Cleanup"
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }} teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
Test-Site-Data-Cleanup: Test-Site-Data-Cleanup:
@ -47,7 +50,7 @@ jobs:
continue-on-error: true continue-on-error: true
strategy: strategy:
matrix: matrix:
site: [ CORSO_M365_TEST_SITE_URL, CORSO_M365_TEST_GROUPS_SITE_URL ] site: [CORSO_M365_TEST_SITE_URL, CORSO_M365_TEST_GROUPS_SITE_URL]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -70,10 +73,13 @@ jobs:
azure-tenant-id: ${{ secrets.TENANT_ID }} azure-tenant-id: ${{ secrets.TENANT_ID }}
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }} m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }} m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
tenant-domain: ${{ vars.TENANT_DOMAIN }}
- name: Notify failure in teams - name: Notify failure in teams
if: failure() if: failure()
uses: ./.github/actions/teams-message uses: ./.github/actions/teams-message
with: with:
msg: "[FAILED] ${{ vars[matrix.site] }} CI Cleanup" msg: "[CORSO FAILED] ${{ vars[matrix.site] }} CI Cleanup"
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }} teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}

View File

@ -155,3 +155,6 @@ jobs:
azure-tenant-id: ${{ secrets.TENANT_ID }} azure-tenant-id: ${{ secrets.TENANT_ID }}
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }} m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }} m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
tenant-domain: ${{ vars.TENANT_DOMAIN }}

View File

@ -6,7 +6,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
user: user:
description: 'User to run longevity test on' description: "User to run longevity test on"
permissions: permissions:
# required to retrieve AWS credentials # required to retrieve AWS credentials
@ -23,7 +23,7 @@ jobs:
uses: alcionai/corso/.github/workflows/accSelector.yaml@main uses: alcionai/corso/.github/workflows/accSelector.yaml@main
Longevity-Tests: Longevity-Tests:
needs: [ SetM365App ] needs: [SetM365App]
environment: Testing environment: Testing
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@ -37,7 +37,7 @@ jobs:
CORSO_LOG_FILE: ${{ github.workspace }}/src/testlog/run-longevity.log CORSO_LOG_FILE: ${{ github.workspace }}/src/testlog/run-longevity.log
RESTORE_DEST_PFX: Corso_Test_Longevity_ RESTORE_DEST_PFX: Corso_Test_Longevity_
TEST_USER: ${{ github.event.inputs.user != '' && github.event.inputs.user || vars.CORSO_M365_TEST_USER_ID }} TEST_USER: ${{ github.event.inputs.user != '' && github.event.inputs.user || vars.CORSO_M365_TEST_USER_ID }}
PREFIX: 'longevity' PREFIX: "longevity"
# Options for retention. # Options for retention.
RETENTION_MODE: GOVERNANCE RETENTION_MODE: GOVERNANCE
@ -46,7 +46,7 @@ jobs:
defaults: defaults:
run: run:
working-directory: src working-directory: src
############################################################################ ############################################################################
# setup # setup
steps: steps:
@ -78,7 +78,7 @@ jobs:
- run: go build -o corso - run: go build -o corso
timeout-minutes: 10 timeout-minutes: 10
- run: mkdir ${CORSO_LOG_DIR} - run: mkdir ${CORSO_LOG_DIR}
# Use shorter-lived credentials obtained from assume-role since these # Use shorter-lived credentials obtained from assume-role since these
@ -163,7 +163,7 @@ jobs:
data=$( echo $resultjson | jq -r '.[0] | .id' ) data=$( echo $resultjson | jq -r '.[0] | .id' )
echo result=$data >> $GITHUB_OUTPUT echo result=$data >> $GITHUB_OUTPUT
########################################################################## ##########################################################################
# Onedrive # Onedrive
@ -328,7 +328,7 @@ jobs:
--hide-progress \ --hide-progress \
--force \ --force \
--json \ --json \
2>&1 | tee ${{ env.CORSO_LOG_DIR }}/maintenance_metadata.txt 2>&1 | tee ${{ env.CORSO_LOG_DIR }}/maintenance_metadata.txt
- name: Maintenance test Weekly - name: Maintenance test Weekly
id: maintenance-test-weekly id: maintenance-test-weekly
@ -392,5 +392,5 @@ jobs:
if: failure() if: failure()
uses: ./.github/actions/teams-message uses: ./.github/actions/teams-message
with: with:
msg: "[FAILED] Longevity Test" msg: "[CORSO FAILED] Longevity Test"
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }} teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}

View File

@ -48,7 +48,7 @@ jobs:
# ---------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------
Test-Suite-Trusted: Test-Suite-Trusted:
needs: [ Checkout, SetM365App] needs: [Checkout, SetM365App]
environment: Testing environment: Testing
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
@ -100,9 +100,9 @@ jobs:
-timeout 2h \ -timeout 2h \
./... 2>&1 | tee ./testlog/gotest-nightly.log | gotestfmt -hide successful-tests ./... 2>&1 | tee ./testlog/gotest-nightly.log | gotestfmt -hide successful-tests
########################################################################################################################################## ##########################################################################################################################################
# Logging & Notifications # Logging & Notifications
# Upload the original go test output as an artifact for later review. # Upload the original go test output as an artifact for later review.
- name: Upload test log - name: Upload test log
@ -118,5 +118,5 @@ jobs:
if: failure() if: failure()
uses: ./.github/actions/teams-message uses: ./.github/actions/teams-message
with: with:
msg: "[FAILED] Nightly Checks" msg: "[COROS FAILED] Nightly Checks"
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }} teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}

View File

@ -6,7 +6,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
user: user:
description: 'User to run sanity test on' description: "User to run sanity test on"
permissions: permissions:
# required to retrieve AWS credentials # required to retrieve AWS credentials
@ -23,7 +23,7 @@ jobs:
uses: alcionai/corso/.github/workflows/accSelector.yaml@main uses: alcionai/corso/.github/workflows/accSelector.yaml@main
Sanity-Tests: Sanity-Tests:
needs: [ SetM365App ] needs: [SetM365App]
environment: Testing environment: Testing
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@ -43,12 +43,11 @@ jobs:
defaults: defaults:
run: run:
working-directory: src working-directory: src
##########################################################################################################################################
# setup ##########################################################################################################################################
# setup
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Golang with cache - name: Setup Golang with cache
@ -64,9 +63,9 @@ jobs:
- run: mkdir ${CORSO_LOG_DIR} - run: mkdir ${CORSO_LOG_DIR}
########################################################################################################################################## ##########################################################################################################################################
# Pre-Run cleanup # Pre-Run cleanup
# unlike CI tests, sanity tests are not expected to run concurrently. # unlike CI tests, sanity tests are not expected to run concurrently.
# however, the sanity yaml concurrency is set to a maximum of 1 run, preferring # however, the sanity yaml concurrency is set to a maximum of 1 run, preferring
@ -91,6 +90,9 @@ jobs:
azure-tenant-id: ${{ secrets.TENANT_ID }} azure-tenant-id: ${{ secrets.TENANT_ID }}
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }} m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }} m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
tenant-domain: ${{ vars.TENANT_DOMAIN }}
- name: Purge CI-Produced Folders for Sites - name: Purge CI-Produced Folders for Sites
timeout-minutes: 30 timeout-minutes: 30
@ -99,17 +101,20 @@ jobs:
with: with:
site: ${{ vars.CORSO_M365_TEST_SITE_URL }} site: ${{ vars.CORSO_M365_TEST_SITE_URL }}
folder-prefix: ${{ env.RESTORE_DEST_PFX }} folder-prefix: ${{ env.RESTORE_DEST_PFX }}
libraries: ${{ vars.CORSO_M365_TEST_SITE_LIBRARIES }} libraries: ${{ vars.CORSO_M365_TEST_SITE_LIBRARIES }}
older-than: ${{ env.NOW }} older-than: ${{ env.NOW }}
azure-client-id: ${{ secrets[needs.SetM365App.outputs.client_id_env] }} azure-client-id: ${{ secrets[needs.SetM365App.outputs.client_id_env] }}
azure-client-secret: ${{ secrets[needs.SetM365App.outputs.client_secret_env] }} azure-client-secret: ${{ secrets[needs.SetM365App.outputs.client_secret_env] }}
azure-tenant-id: ${{ secrets.TENANT_ID }} azure-tenant-id: ${{ secrets.TENANT_ID }}
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }} m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }} m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
tenant-domain: ${{ vars.TENANT_DOMAIN }}
########################################################################################################################################## ##########################################################################################################################################
# Repository commands # Repository commands
- name: Version Test - name: Version Test
timeout-minutes: 10 timeout-minutes: 10
@ -169,9 +174,9 @@ jobs:
--mode complete \ --mode complete \
2>&1 | tee ${{ env.CORSO_LOG_DIR }}/gotest-repo-maintenance.log 2>&1 | tee ${{ env.CORSO_LOG_DIR }}/gotest-repo-maintenance.log
########################################################################################################################################## ##########################################################################################################################################
# Exchange # Exchange
# generate new entries to roll into the next load test # generate new entries to roll into the next load test
# only runs if the test was successful # only runs if the test was successful
@ -193,8 +198,8 @@ jobs:
service: exchange service: exchange
kind: first-backup kind: first-backup
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"' backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
@ -206,8 +211,8 @@ jobs:
service: exchange service: exchange
kind: incremental kind: incremental
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"' backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
backup-id: ${{ steps.exchange-backup.outputs.backup-id }} backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
@ -220,8 +225,8 @@ jobs:
service: exchange service: exchange
kind: non-delta kind: non-delta
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email" --disable-delta' backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email" --disable-delta'
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
backup-id: ${{ steps.exchange-backup.outputs.backup-id }} backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
@ -234,16 +239,15 @@ jobs:
service: exchange service: exchange
kind: non-delta-incremental kind: non-delta-incremental
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"' backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
backup-id: ${{ steps.exchange-backup.outputs.backup-id }} backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
##########################################################################################################################################
########################################################################################################################################## # Onedrive
# Onedrive
# generate new entries for test # generate new entries for test
- name: OneDrive - Create new data - name: OneDrive - Create new data
@ -270,8 +274,8 @@ jobs:
service: onedrive service: onedrive
kind: first-backup kind: first-backup
backup-args: '--user "${{ env.TEST_USER }}"' backup-args: '--user "${{ env.TEST_USER }}"'
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}' restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
@ -295,14 +299,14 @@ jobs:
service: onedrive service: onedrive
kind: incremental kind: incremental
backup-args: '--user "${{ env.TEST_USER }}"' backup-args: '--user "${{ env.TEST_USER }}"'
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}' restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
########################################################################################################################################## ##########################################################################################################################################
# Sharepoint Library # Sharepoint Library
# generate new entries for test # generate new entries for test
- name: SharePoint - Create new data - name: SharePoint - Create new data
@ -330,8 +334,8 @@ jobs:
service: sharepoint service: sharepoint
kind: first-backup kind: first-backup
backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries' backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries'
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}' restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
category: libraries category: libraries
@ -357,15 +361,15 @@ jobs:
service: sharepoint service: sharepoint
kind: incremental kind: incremental
backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries' backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries'
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}' restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
category: libraries category: libraries
########################################################################################################################################## ##########################################################################################################################################
# Sharepoint Lists # Sharepoint Lists
# generate new entries for test # generate new entries for test
# The `awk | tr | sed` command chain is used to get a comma separated list of SharePoint list names. # The `awk | tr | sed` command chain is used to get a comma separated list of SharePoint list names.
@ -418,7 +422,7 @@ jobs:
working-directory: ./src/cmd/factory working-directory: ./src/cmd/factory
run: | run: |
suffix=$(date +"%Y-%m-%d_%H-%M-%S") suffix=$(date +"%Y-%m-%d_%H-%M-%S")
go run . sharepoint lists \ go run . sharepoint lists \
--site ${{ vars.CORSO_M365_TEST_SITE_URL }} \ --site ${{ vars.CORSO_M365_TEST_SITE_URL }} \
--user ${{ env.TEST_USER }} \ --user ${{ env.TEST_USER }} \
@ -454,9 +458,9 @@ jobs:
category: lists category: lists
on-collision: copy on-collision: copy
########################################################################################################################################## ##########################################################################################################################################
# Groups and Teams # Groups and Teams
# generate new entries for test # generate new entries for test
- name: Groups - Create new data - name: Groups - Create new data
@ -483,8 +487,8 @@ jobs:
with: with:
service: groups service: groups
kind: first-backup kind: first-backup
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}"' backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}" --data messages,libraries'
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
@ -508,15 +512,15 @@ jobs:
with: with:
service: groups service: groups
kind: incremental kind: incremental
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}"' backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}" --data messages,libraries'
restore-args: '--site "${{ vars.CORSO_M365_TEST_GROUPS_SITE_URL }}" --folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}' restore-args: '--site "${{ vars.CORSO_M365_TEST_GROUPS_SITE_URL }}" --folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}' restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}"
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
with-export: true with-export: true
########################################################################################################################################## ##########################################################################################################################################
# Logging & Notifications # Logging & Notifications
# Upload the original go test output as an artifact for later review. # Upload the original go test output as an artifact for later review.
- name: Upload test log - name: Upload test log
@ -532,5 +536,5 @@ jobs:
if: failure() if: failure()
uses: ./.github/actions/teams-message uses: ./.github/actions/teams-message
with: with:
msg: "[FAILED] Sanity Tests" msg: "[CORSO FAILED] Sanity Tests"
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }} teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}

View File

@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Handle the case where an email or event cannot be retrieved from Exchange due to an `ErrorCorruptData` error. Corso will skip over the item but report it in the backup summary. - Handle the case where an email or event cannot be retrieved from Exchange due to an `ErrorCorruptData` error. Corso will skip over the item but report it in the backup summary.
- Emails attached within other emails are now correctly exported - Emails attached within other emails are now correctly exported
- Gracefully handle email and post attachments without name when exporting to eml - Gracefully handle email and post attachments without name when exporting to eml
- Use correct timezone for event start and end times in Exchange exports (helps fix issues in relative recurrence patterns)
- Fixed an issue causing exports dealing with calendar data to have high memory usage
## [v0.19.0] (beta) - 2024-02-06 ## [v0.19.0] (beta) - 2024-02-06

View File

@ -1,3 +1,6 @@
> [!NOTE]
> **The Corso project is no longer actively maintained and has been archived**.
<p align="center"> <p align="center">
<img src="https://github.com/alcionai/corso/blob/main/website/static/img/corso_logo.svg?raw=true" alt="Corso Logo" width="100" /> <img src="https://github.com/alcionai/corso/blob/main/website/static/img/corso_logo.svg?raw=true" alt="Corso Logo" width="100" />
</p> </p>

View File

@ -150,8 +150,11 @@ func runExchangeBackupCategoryTest(suite *BackupExchangeE2ESuite, category path.
result := recorder.String() result := recorder.String()
t.Log("backup results", result) t.Log("backup results", result)
// as an offhand check: the result should contain the m365 user id // As an offhand check: the result should contain the m365 user's email.
assert.Contains(t, result, suite.m365.User.ID) assert.Contains(
t,
strings.ToLower(result),
strings.ToLower(suite.m365.User.Provider.Name()))
} }
func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_ServiceNotEnabled_email() { func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_ServiceNotEnabled_email() {
@ -183,8 +186,11 @@ func runExchangeBackupServiceNotEnabledTest(suite *BackupExchangeE2ESuite, categ
result := recorder.String() result := recorder.String()
t.Log("backup results", result) t.Log("backup results", result)
// as an offhand check: the result should contain the m365 user id // As an offhand check: the result should contain the m365 user's email.
assert.Contains(t, result, suite.m365.User.ID) assert.Contains(
t,
strings.ToLower(result),
strings.ToLower(suite.m365.User.Provider.Name()))
} }
func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_userNotFound_email() { func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_userNotFound_email() {
@ -282,8 +288,11 @@ func (suite *BackupExchangeE2ESuite) TestBackupCreateExchange_fromConfigFile() {
result := suite.dpnd.recorder.String() result := suite.dpnd.recorder.String()
t.Log("backup results", result) t.Log("backup results", result)
// as an offhand check: the result should contain the m365 user id // As an offhand check: the result should contain the m365 user's email.
assert.Contains(t, result, suite.m365.User.ID) assert.Contains(
t,
strings.ToLower(result),
strings.ToLower(suite.m365.User.Provider.Name()))
} }
// AWS flags // AWS flags

View File

@ -114,6 +114,8 @@ func (suite *BackupGroupsE2ESuite) TestGroupsBackupCmd_channelMessages() {
} }
func (suite *BackupGroupsE2ESuite) TestGroupsBackupCmd_conversations() { func (suite *BackupGroupsE2ESuite) TestGroupsBackupCmd_conversations() {
// skip
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
runGroupsBackupCategoryTest(suite, flags.DataConversations) runGroupsBackupCategoryTest(suite, flags.DataConversations)
} }
@ -217,6 +219,9 @@ func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_badAzureClientIDFlag()
} }
func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_fromConfigFile() { func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_fromConfigFile() {
// Skip
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
t := suite.T() t := suite.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
ctx = config.SetViper(ctx, suite.dpnd.vpr) ctx = config.SetViper(ctx, suite.dpnd.vpr)
@ -300,7 +305,10 @@ func (suite *PreparedBackupGroupsE2ESuite) SetupSuite() {
ins = idname.NewCache(map[string]string{suite.m365.Group.ID: suite.m365.Group.ID}) ins = idname.NewCache(map[string]string{suite.m365.Group.ID: suite.m365.Group.ID})
cats = []path.CategoryType{ cats = []path.CategoryType{
path.ChannelMessagesCategory, path.ChannelMessagesCategory,
path.ConversationPostsCategory, // TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
// odata.NextLink which causes an infinite loop during paging. Disabling conversations tests while
// we go fix the group mailbox.
// path.ConversationPostsCategory,
path.LibrariesCategory, path.LibrariesCategory,
} }
) )
@ -454,6 +462,8 @@ func (suite *PreparedBackupGroupsE2ESuite) TestGroupsDetailsCmd_channelMessages(
} }
func (suite *PreparedBackupGroupsE2ESuite) TestGroupsDetailsCmd_conversations() { func (suite *PreparedBackupGroupsE2ESuite) TestGroupsDetailsCmd_conversations() {
// skip
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
runGroupsDetailsCmdTest(suite, path.ConversationPostsCategory) runGroupsDetailsCmdTest(suite, path.ConversationPostsCategory)
} }

View File

@ -6,12 +6,6 @@ Param (
[Parameter(Mandatory = $False, HelpMessage = "Site for which to delete folders in SharePoint")] [Parameter(Mandatory = $False, HelpMessage = "Site for which to delete folders in SharePoint")]
[String]$Site, [String]$Site,
[Parameter(Mandatory = $False, HelpMessage = "Exchange Admin email")]
[String]$AdminUser = $ENV:M365_TENANT_ADMIN_USER,
[Parameter(Mandatory = $False, HelpMessage = "Exchange Admin password")]
[String]$AdminPwd = $ENV:M365_TENANT_ADMIN_PASSWORD,
[Parameter(Mandatory = $False, HelpMessage = "Document library root. Can add multiple comma-separated values")] [Parameter(Mandatory = $False, HelpMessage = "Document library root. Can add multiple comma-separated values")]
[String[]]$LibraryNameList = @(), [String[]]$LibraryNameList = @(),
@ -22,7 +16,16 @@ Param (
[String[]]$FolderPrefixPurgeList, [String[]]$FolderPrefixPurgeList,
[Parameter(Mandatory = $False, HelpMessage = "Delete document libraries with this prefix")] [Parameter(Mandatory = $False, HelpMessage = "Delete document libraries with this prefix")]
[String[]]$LibraryPrefixDeleteList = @() [String[]]$LibraryPrefixDeleteList = @(),
[Parameter(Mandatory = $False, HelpMessage = "Tenant domain")]
[String]$TenantDomain = $ENV:TENANT_DOMAIN,
[Parameter(Mandatory = $False, HelpMessage = "Azure ClientId")]
[String]$ClientId = $ENV:AZURE_CLIENT_ID,
[Parameter(Mandatory = $False, HelpMessage = "Azure AppCert")]
[String]$AppCert = $ENV:AZURE_APP_CERT
) )
Set-StrictMode -Version 2.0 Set-StrictMode -Version 2.0
@ -37,7 +40,7 @@ function Get-TimestampFromFolderName {
$name = $folder.Name $name = $folder.Name
#fallback on folder create time #fallback on folder create time
[datetime]$timestamp = $folder.TimeCreated [datetime]$timestamp = $folder.TimeCreated
try { try {
@ -66,7 +69,7 @@ function Get-TimestampFromListName {
$name = $list.Title $name = $list.Title
#fallback on list create time #fallback on list create time
[datetime]$timestamp = $list.LastItemUserModifiedDate [datetime]$timestamp = $list.LastItemUserModifiedDate
try { try {
@ -106,8 +109,9 @@ function Purge-Library {
Write-Host "`nPurging library: $LibraryName" Write-Host "`nPurging library: $LibraryName"
$foldersToPurge = @() $foldersToPurge = @()
$folders = Get-PnPFolderItem -FolderSiteRelativeUrl $LibraryName -ItemType Folder $folders = Get-PnPFolderItem -FolderSiteRelativeUrl $LibraryName -ItemType Folder
Write-Host "`nFolders: $folders"
foreach ($f in $folders) { foreach ($f in $folders) {
$folderName = $f.Name $folderName = $f.Name
$createTime = Get-TimestampFromFolderName -Folder $f $createTime = Get-TimestampFromFolderName -Folder $f
@ -159,7 +163,7 @@ function Delete-LibraryByPrefix {
Write-Host "`nDeleting library: $LibraryNamePrefix" Write-Host "`nDeleting library: $LibraryNamePrefix"
$listsToDelete = @() $listsToDelete = @()
$lists = Get-PnPList $lists = Get-PnPList
foreach ($l in $lists) { foreach ($l in $lists) {
$listName = $l.Title $listName = $l.Title
@ -183,7 +187,7 @@ function Delete-LibraryByPrefix {
Write-Host "Deleting list: "$l.Title Write-Host "Deleting list: "$l.Title
try { try {
$listInfo = Get-PnPList -Identity $l.Id | Select-Object -Property Hidden $listInfo = Get-PnPList -Identity $l.Id | Select-Object -Property Hidden
# Check if the 'hidden' property is true # Check if the 'hidden' property is true
if ($listInfo.Hidden) { if ($listInfo.Hidden) {
Write-Host "List: $($l.Title) is hidden. Skipping..." Write-Host "List: $($l.Title) is hidden. Skipping..."
@ -209,8 +213,8 @@ if (-not (Get-Module -ListAvailable -Name PnP.PowerShell)) {
} }
if ([string]::IsNullOrEmpty($AdminUser) -or [string]::IsNullOrEmpty($AdminPwd)) { if ([string]::IsNullOrEmpty($ClientId) -or [string]::IsNullOrEmpty($AppCert)) {
Write-Host "Admin user name and password required as arguments or environment variables." Write-Host "ClientId and AppCert required as arguments or environment variables."
Exit Exit
} }
@ -251,12 +255,8 @@ else {
Exit Exit
} }
$password = convertto-securestring -String $AdminPwd -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AdminUser, $password
Write-Host "`nAuthenticating and connecting to $SiteUrl" Write-Host "`nAuthenticating and connecting to $SiteUrl"
Connect-PnPOnline -Url $siteUrl -Credential $cred Connect-PnPOnline -Url $siteUrl -ClientId $ClientId -CertificateBase64Encoded $AppCert -Tenant $TenantDomain
Write-Host "Connected to $siteUrl`n" Write-Host "Connected to $siteUrl`n"
# ensure that there are no unexpanded entries in the list of parameters # ensure that there are no unexpanded entries in the list of parameters

View File

@ -5,6 +5,7 @@ import (
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"golang.org/x/exp/slices"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
@ -20,19 +21,20 @@ const (
// this increases the chance that we'll run into a race collision with // this increases the chance that we'll run into a race collision with
// the cleanup script. Sometimes that's okay (deleting old data that // the cleanup script. Sometimes that's okay (deleting old data that
// isn't scrutinized in the test), other times it's not. We mark whether // isn't scrutinized in the test), other times it's not. We mark whether
// that's okay to do or not by specifying the folder that's being // that's okay to do or not by specifying the folders being
// scrutinized for the test. Any errors within that folder should cause // scrutinized for the test. Any errors within those folders should cause
// a fatal exit. Errors outside of that folder get ignored. // a fatal exit. Errors outside of those folders get ignored.
// //
// since we're using folder names, requireNoErrorsWithinFolderName will // since we're using folder names, mustPopulateFolders will
// work best (ie: have the fewest collisions/side-effects) if the folder // work best (ie: have the fewest collisions/side-effects) if the folder
// name is very specific. Standard sanity tests should include timestamps, // names are very specific. Standard sanity tests should include timestamps,
// which should help ensure that. Be warned if you try to use it with // which should help ensure that. Be warned if you try to use it with
// a more generic name: unintended effects could occur. // a more generic name: unintended effects could occur.
func populateSanitree( func populateSanitree(
ctx context.Context, ctx context.Context,
ac api.Client, ac api.Client,
driveID, requireNoErrorsWithinFolderName string, driveID string,
mustPopulateFolders []string,
) *common.Sanitree[models.DriveItemable, models.DriveItemable] { ) *common.Sanitree[models.DriveItemable, models.DriveItemable] {
common.Infof(ctx, "building sanitree for drive: %s", driveID) common.Infof(ctx, "building sanitree for drive: %s", driveID)
@ -56,8 +58,8 @@ func populateSanitree(
ac, ac,
driveID, driveID,
stree.Name+"/", stree.Name+"/",
requireNoErrorsWithinFolderName, mustPopulateFolders,
rootName == requireNoErrorsWithinFolderName, slices.Contains(mustPopulateFolders, rootName),
stree) stree)
return stree return stree
@ -66,7 +68,9 @@ func populateSanitree(
func recursivelyBuildTree( func recursivelyBuildTree(
ctx context.Context, ctx context.Context,
ac api.Client, ac api.Client,
driveID, location, requireNoErrorsWithinFolderName string, driveID string,
location string,
mustPopulateFolders []string,
isChildOfFolderRequiringNoErrors bool, isChildOfFolderRequiringNoErrors bool,
stree *common.Sanitree[models.DriveItemable, models.DriveItemable], stree *common.Sanitree[models.DriveItemable, models.DriveItemable],
) { ) {
@ -80,9 +84,9 @@ func recursivelyBuildTree(
common.Infof( common.Infof(
ctx, ctx,
"ignoring error getting children in directory %q because it is not within directory %q\nerror: %s\n%+v", "ignoring error getting children in directory %q because it is not within directory set %v\nerror: %s\n%+v",
location, location,
requireNoErrorsWithinFolderName, mustPopulateFolders,
err.Error(), err.Error(),
clues.ToCore(err)) clues.ToCore(err))
@ -99,11 +103,12 @@ func recursivelyBuildTree(
// currently we don't restore blank folders. // currently we don't restore blank folders.
// skip permission check for empty folders // skip permission check for empty folders
if ptr.Val(driveItem.GetFolder().GetChildCount()) == 0 { if ptr.Val(driveItem.GetFolder().GetChildCount()) == 0 {
common.Infof(ctx, "skipped empty folder: %s/%s", location, itemName) common.Infof(ctx, "skipped empty folder: %s%s", location, itemName)
continue continue
} }
cannotAllowErrors := isChildOfFolderRequiringNoErrors || itemName == requireNoErrorsWithinFolderName cannotAllowErrors := isChildOfFolderRequiringNoErrors ||
slices.Contains(mustPopulateFolders, itemName)
branch := &common.Sanitree[models.DriveItemable, models.DriveItemable]{ branch := &common.Sanitree[models.DriveItemable, models.DriveItemable]{
Parent: stree, Parent: stree,
@ -124,7 +129,7 @@ func recursivelyBuildTree(
ac, ac,
driveID, driveID,
location+branch.Name+"/", location+branch.Name+"/",
requireNoErrorsWithinFolderName, mustPopulateFolders,
cannotAllowErrors, cannotAllowErrors,
branch) branch)
} }

View File

@ -32,7 +32,7 @@ func CheckExport(
ctx, ctx,
ac, ac,
driveID, driveID,
envs.RestoreContainer) []string{envs.SourceContainer})
sourceTree, ok := root.Children[envs.SourceContainer] sourceTree, ok := root.Children[envs.SourceContainer]
common.Assert( common.Assert(

View File

@ -45,7 +45,14 @@ func CheckRestoration(
"drive_id", driveID, "drive_id", driveID,
"drive_name", driveName) "drive_name", driveName)
root := populateSanitree(ctx, ac, driveID, envs.RestoreContainer) root := populateSanitree(
ctx,
ac,
driveID,
[]string{
envs.SourceContainer,
envs.RestoreContainer,
})
sourceTree, ok := root.Children[envs.SourceContainer] sourceTree, ok := root.Children[envs.SourceContainer]
common.Assert( common.Assert(

View File

@ -3,7 +3,7 @@ module github.com/alcionai/corso/src
go 1.21 go 1.21
replace ( replace (
github.com/kopia/kopia => github.com/alcionai/kopia v0.12.2-0.20240116215733-ec3d100029fe github.com/kopia/kopia => github.com/alcionai/kopia v0.12.2-0.20240322180947-41471159a0a4
// Alcion fork removes the validation of email addresses as we might get incomplete email addresses // Alcion fork removes the validation of email addresses as we might get incomplete email addresses
github.com/xhit/go-simple-mail/v2 v2.16.0 => github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a github.com/xhit/go-simple-mail/v2 v2.16.0 => github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a
@ -121,7 +121,7 @@ require (
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/microsoft/kiota-serialization-text-go v1.0.0 github.com/microsoft/kiota-serialization-text-go v1.0.0
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.66 github.com/minio/minio-go/v7 v7.0.67
github.com/minio/sha256-simd v1.0.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect

View File

@ -23,8 +23,8 @@ github.com/alcionai/clues v0.0.0-20240125221452-9fc7746dd20c h1:QtARFaqYKtGjmEej
github.com/alcionai/clues v0.0.0-20240125221452-9fc7746dd20c/go.mod h1:1YJwJy3W6GGsC2UiDAEWABUjgvT8OZHjKs8OoaXeKbw= github.com/alcionai/clues v0.0.0-20240125221452-9fc7746dd20c/go.mod h1:1YJwJy3W6GGsC2UiDAEWABUjgvT8OZHjKs8OoaXeKbw=
github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a h1:4nhM0NM1qtUT1s55rQ+D0Xw1Re5mIU9/crjEl6KdE+k= github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a h1:4nhM0NM1qtUT1s55rQ+D0Xw1Re5mIU9/crjEl6KdE+k=
github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/alcionai/kopia v0.12.2-0.20240116215733-ec3d100029fe h1:nLS5pxhm04Jz4+qeipNlxdyPGxqNWpBu8UGkRYpWoIw= github.com/alcionai/kopia v0.12.2-0.20240322180947-41471159a0a4 h1:3YZ70H3mkUgwiHLiNvukrqh2awRgfl1RAkbV0IoUqqk=
github.com/alcionai/kopia v0.12.2-0.20240116215733-ec3d100029fe/go.mod h1:QFRSOUQzZfKE3hKVBHP7hxOn5WyrEmdBtfN5wkib/eA= github.com/alcionai/kopia v0.12.2-0.20240322180947-41471159a0a4/go.mod h1:QFRSOUQzZfKE3hKVBHP7hxOn5WyrEmdBtfN5wkib/eA=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -219,8 +219,8 @@ github.com/microsoftgraph/msgraph-sdk-go-core v1.0.1 h1:uq4qZD8VXLiNZY0t4NoRpLDo
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.1/go.mod h1:HUITyuFN556+0QZ/IVfH5K4FyJM7kllV6ExKi2ImKhE= github.com/microsoftgraph/msgraph-sdk-go-core v1.0.1/go.mod h1:HUITyuFN556+0QZ/IVfH5K4FyJM7kllV6ExKi2ImKhE=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw= github.com/minio/minio-go/v7 v7.0.67 h1:BeBvZWAS+kRJm1vGTMJYVjKUNoo0FoEt/wUWdUtfmh8=
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs= github.com/minio/minio-go/v7 v7.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/pkg/dttm" "github.com/alcionai/corso/src/pkg/dttm"
"github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/export"
"github.com/alcionai/corso/src/pkg/logger"
) )
const ( const (
@ -56,12 +57,22 @@ func ZipExportCollection(
defer wr.Close() defer wr.Close()
buf := make([]byte, ZipCopyBufferSize) buf := make([]byte, ZipCopyBufferSize)
counted := 0
log := logger.Ctx(ctx).
With("collection_count", len(expCollections))
for _, ec := range expCollections { for _, ec := range expCollections {
folder := ec.BasePath() folder := ec.BasePath()
items := ec.Items(ctx) items := ec.Items(ctx)
for item := range items { for item := range items {
counted++
// Log every 1000 items that are processed
if counted%1000 == 0 {
log.Infow("progress zipping export items", "count_items", counted)
}
err := item.Error err := item.Error
if err != nil { if err != nil {
writer.CloseWithError(clues.Wrap(err, "getting export item").With("id", item.ID)) writer.CloseWithError(clues.Wrap(err, "getting export item").With("id", item.ID))
@ -88,8 +99,12 @@ func ZipExportCollection(
writer.CloseWithError(clues.Wrap(err, "writing zip entry").With("name", name).With("id", item.ID)) writer.CloseWithError(clues.Wrap(err, "writing zip entry").With("name", name).With("id", item.ID))
return return
} }
item.Body.Close()
} }
} }
log.Infow("completed zipping export items", "count_items", counted)
}() }()
return zipCollection{reader}, nil return zipCollection{reader}, nil

View File

@ -1,10 +1,13 @@
package jwt package jwt
import ( import (
"context"
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
jwt "github.com/golang-jwt/jwt/v5" jwt "github.com/golang-jwt/jwt/v5"
"github.com/alcionai/corso/src/pkg/logger"
) )
// IsJWTExpired checks if the JWT token is past expiry by analyzing the // IsJWTExpired checks if the JWT token is past expiry by analyzing the
@ -37,3 +40,51 @@ func IsJWTExpired(
return expired, nil return expired, nil
} }
// GetJWTLifetime returns the issued at(iat) and expiration time(exp) claims
// present in the JWT token. These are optional claims and may not be present
// in the token. Absence is not reported as an error.
//
// An error is returned if the supplied token is malformed. Times are returned
// in UTC to have parity with graph responses.
func GetJWTLifetime(
ctx context.Context,
rawToken string,
) (time.Time, time.Time, error) {
var (
issuedAt time.Time
expiresAt time.Time
)
p := jwt.NewParser()
token, _, err := p.ParseUnverified(rawToken, &jwt.RegisteredClaims{})
if err != nil {
logger.CtxErr(ctx, err).Debug("parsing jwt token")
return time.Time{}, time.Time{}, clues.Wrap(err, "invalid jwt")
}
exp, err := token.Claims.GetExpirationTime()
if err != nil {
logger.CtxErr(ctx, err).Debug("extracting exp claim")
return time.Time{}, time.Time{}, clues.Wrap(err, "getting token expiry time")
}
iat, err := token.Claims.GetIssuedAt()
if err != nil {
logger.CtxErr(ctx, err).Debug("extracting iat claim")
return time.Time{}, time.Time{}, clues.Wrap(err, "getting token issued at time")
}
// Absence of iat or exp claims is not reported as an error by jwt library as these
// are optional as per spec.
if iat != nil {
issuedAt = iat.UTC()
}
if exp != nil {
expiresAt = exp.UTC()
}
return issuedAt, expiresAt, nil
}

View File

@ -113,3 +113,134 @@ func (suite *JWTUnitSuite) TestIsJWTExpired() {
}) })
} }
} }
func (suite *JWTUnitSuite) TestGetJWTLifetime() {
// Set of time values to be used in the tests.
// Truncate to seconds for comparisons since jwt tokens have second
// level precision.
idToTime := map[string]time.Time{
"T0": time.Now().UTC().Add(-time.Hour).Truncate(time.Second),
"T1": time.Now().UTC().Truncate(time.Second),
"T2": time.Now().UTC().Add(time.Hour).Truncate(time.Second),
}
table := []struct {
name string
getToken func() (string, error)
expectFunc func(t *testing.T, iat time.Time, exp time.Time)
expectErr assert.ErrorAssertionFunc
}{
{
name: "alive token",
getToken: func() (string, error) {
return createJWTToken(
jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(idToTime["T0"]),
ExpiresAt: jwt.NewNumericDate(idToTime["T1"]),
})
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Equal(t, idToTime["T0"], iat)
assert.Equal(t, idToTime["T1"], exp)
},
expectErr: assert.NoError,
},
// Test with a token which is not generated using the go-jwt lib.
// This is a long lived token which is valid for 100 years.
{
name: "alive raw token with iat and exp claims",
getToken: func() (string, error) {
return rawToken, nil
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Less(t, iat, time.Now(), "iat should be in the past")
assert.Greater(t, exp, time.Now(), "exp should be in the future")
},
expectErr: assert.NoError,
},
// Regardless of whether the token is expired or not, we should be able to
// extract the iat and exp claims from it without error.
{
name: "expired token",
getToken: func() (string, error) {
return createJWTToken(
jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(idToTime["T1"]),
ExpiresAt: jwt.NewNumericDate(idToTime["T0"]),
})
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Equal(t, idToTime["T1"], iat)
assert.Equal(t, idToTime["T0"], exp)
},
expectErr: assert.NoError,
},
{
name: "missing iat claim",
getToken: func() (string, error) {
return createJWTToken(
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(idToTime["T2"]),
})
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Equal(t, time.Time{}, iat)
assert.Equal(t, idToTime["T2"], exp)
},
expectErr: assert.NoError,
},
{
name: "missing exp claim",
getToken: func() (string, error) {
return createJWTToken(
jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(idToTime["T0"]),
})
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Equal(t, idToTime["T0"], iat)
assert.Equal(t, time.Time{}, exp)
},
expectErr: assert.NoError,
},
{
name: "both claims missing",
getToken: func() (string, error) {
return createJWTToken(jwt.RegisteredClaims{})
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Equal(t, time.Time{}, iat)
assert.Equal(t, time.Time{}, exp)
},
expectErr: assert.NoError,
},
{
name: "malformed token",
getToken: func() (string, error) {
return "header.claims.signature", nil
},
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
assert.Equal(t, time.Time{}, iat)
assert.Equal(t, time.Time{}, exp)
},
expectErr: assert.Error,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
token, err := test.getToken()
require.NoError(t, err)
iat, exp, err := GetJWTLifetime(ctx, token)
test.expectErr(t, err)
test.expectFunc(t, iat, exp)
})
}
}

View File

@ -59,6 +59,19 @@ func First(vs ...string) string {
return "" return ""
} }
// FirstIn returns the first entry in the map with a non-zero value
// when iterating the provided list of keys.
func FirstIn(m map[string]any, keys ...string) string {
for _, key := range keys {
v, err := AnyValueToString(key, m)
if err == nil && len(v) > 0 {
return v
}
}
return ""
}
// Preview reduces the string to the specified size. // Preview reduces the string to the specified size.
// If the string is longer than the size, the last three // If the string is longer than the size, the last three
// characters are replaced with an ellipsis. Size < 4 // characters are replaced with an ellipsis. Size < 4

View File

@ -118,3 +118,96 @@ func TestGenerateHash(t *testing.T) {
} }
} }
} }
func TestFirstIn(t *testing.T) {
table := []struct {
name string
m map[string]any
keys []string
expect string
}{
{
name: "nil map",
keys: []string{"foo", "bar"},
expect: "",
},
{
name: "empty map",
m: map[string]any{},
keys: []string{"foo", "bar"},
expect: "",
},
{
name: "no match",
m: map[string]any{
"baz": "baz",
},
keys: []string{"foo", "bar"},
expect: "",
},
{
name: "no keys",
m: map[string]any{
"baz": "baz",
},
keys: []string{},
expect: "",
},
{
name: "nil match",
m: map[string]any{
"foo": nil,
},
keys: []string{"foo", "bar"},
expect: "",
},
{
name: "empty match",
m: map[string]any{
"foo": "",
},
keys: []string{"foo", "bar"},
expect: "",
},
{
name: "matches first key",
m: map[string]any{
"foo": "fnords",
},
keys: []string{"foo", "bar"},
expect: "fnords",
},
{
name: "matches second key",
m: map[string]any{
"bar": "smarf",
},
keys: []string{"foo", "bar"},
expect: "smarf",
},
{
name: "matches second key with nil first match",
m: map[string]any{
"foo": nil,
"bar": "smarf",
},
keys: []string{"foo", "bar"},
expect: "smarf",
},
{
name: "matches second key with empty first match",
m: map[string]any{
"foo": "",
"bar": "smarf",
},
keys: []string{"foo", "bar"},
expect: "smarf",
},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
result := FirstIn(test.m, test.keys...)
assert.Equal(t, test.expect, result)
})
}
}

View File

@ -208,6 +208,15 @@ func getItemAttachment(ctx context.Context, attachment models.Attachmentable) (*
With("attachment_id", ptr.Val(attachment.GetId())) With("attachment_id", ptr.Val(attachment.GetId()))
} }
name := ptr.Val(attachment.GetName())
if len(name) == 0 {
// Graph as of now does not let us create any attachments
// without a name, but we have run into instances where we have
// see attachments without a name, possibly from old
// data. This is for those cases.
name = "Unnamed"
}
switch it := it.(type) { switch it := it.(type) {
case *models.Message: case *models.Message:
cb, err := FromMessageable(ctx, it) cb, err := FromMessageable(ctx, it)
@ -217,7 +226,7 @@ func getItemAttachment(ctx context.Context, attachment models.Attachmentable) (*
} }
return &mail.File{ return &mail.File{
Name: ptr.Val(attachment.GetName()), Name: name,
MimeType: "message/rfc822", MimeType: "message/rfc822",
Data: []byte(cb), Data: []byte(cb),
}, nil }, nil

View File

@ -137,6 +137,11 @@ func (suite *EMLUnitSuite) TestConvert_messageble_to_eml() {
} }
func (suite *EMLUnitSuite) TestConvert_edge_cases() { func (suite *EMLUnitSuite) TestConvert_edge_cases() {
bodies := []string{
testdata.EmailWithAttachments,
testdata.EmailWithinEmail,
}
tests := []struct { tests := []struct {
name string name string
transform func(models.Messageable) transform func(models.Messageable)
@ -202,33 +207,35 @@ func (suite *EMLUnitSuite) TestConvert_edge_cases() {
}, },
} }
for _, test := range tests { for _, b := range bodies {
suite.Run(test.name, func() { for _, test := range tests {
t := suite.T() suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
body := []byte(testdata.EmailWithAttachments) body := []byte(b)
msg, err := api.BytesToMessageable(body) msg, err := api.BytesToMessageable(body)
require.NoError(t, err, "creating message") require.NoError(t, err, "creating message")
test.transform(msg) test.transform(msg)
writer := kjson.NewJsonSerializationWriter() writer := kjson.NewJsonSerializationWriter()
defer writer.Close() defer writer.Close()
err = writer.WriteObjectValue("", msg) err = writer.WriteObjectValue("", msg)
require.NoError(t, err, "serializing message") require.NoError(t, err, "serializing message")
nbody, err := writer.GetSerializedContent() nbody, err := writer.GetSerializedContent()
require.NoError(t, err, "getting serialized content") require.NoError(t, err, "getting serialized content")
_, err = FromJSON(ctx, nbody) _, err = FromJSON(ctx, nbody)
assert.NoError(t, err, "converting to eml") assert.NoError(t, err, "converting to eml")
}) })
}
} }
} }
@ -266,11 +273,11 @@ func (suite *EMLUnitSuite) TestConvert_eml_ics() {
assert.Equal( assert.Equal(
t, t,
msg.GetCreatedDateTime().Format(ics.ICalDateTimeFormat), msg.GetCreatedDateTime().Format(ics.ICalDateTimeFormatUTC),
event.GetProperty(ical.ComponentPropertyCreated).Value) event.GetProperty(ical.ComponentPropertyCreated).Value)
assert.Equal( assert.Equal(
t, t,
msg.GetLastModifiedDateTime().Format(ics.ICalDateTimeFormat), msg.GetLastModifiedDateTime().Format(ics.ICalDateTimeFormatUTC),
event.GetProperty(ical.ComponentPropertyLastModified).Value) event.GetProperty(ical.ComponentPropertyLastModified).Value)
st, err := ics.GetUTCTime( st, err := ics.GetUTCTime(
@ -285,11 +292,11 @@ func (suite *EMLUnitSuite) TestConvert_eml_ics() {
assert.Equal( assert.Equal(
t, t,
st.Format(ics.ICalDateTimeFormat), st.Format(ics.ICalDateTimeFormatUTC),
event.GetProperty(ical.ComponentPropertyDtStart).Value) event.GetProperty(ical.ComponentPropertyDtStart).Value)
assert.Equal( assert.Equal(
t, t,
et.Format(ics.ICalDateTimeFormat), et.Format(ics.ICalDateTimeFormatUTC),
event.GetProperty(ical.ComponentPropertyDtEnd).Value) event.GetProperty(ical.ComponentPropertyDtEnd).Value)
tos := msg.GetToRecipients() tos := msg.GetToRecipients()
@ -461,7 +468,7 @@ func (suite *EMLUnitSuite) TestConvert_message_in_messageble_to_eml() {
assert.Equal(t, formatAddress(msg.GetFrom().GetEmailAddress()), eml.GetHeader("From")) assert.Equal(t, formatAddress(msg.GetFrom().GetEmailAddress()), eml.GetHeader("From"))
attachments := eml.Attachments attachments := eml.Attachments
assert.Equal(t, 1, len(attachments), "attachment count in parent email") assert.Equal(t, 3, len(attachments), "attachment count in parent email")
ieml, err := enmime.ReadEnvelope(strings.NewReader(string(attachments[0].Content))) ieml, err := enmime.ReadEnvelope(strings.NewReader(string(attachments[0].Content)))
require.NoError(t, err, "reading created eml") require.NoError(t, err, "reading created eml")

View File

@ -77,6 +77,146 @@
], ],
"webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl0k%3D&exvsurl=1&viewmodel=ItemAttachment" "webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl0k%3D&exvsurl=1&viewmodel=ItemAttachment"
} }
},
{
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAABEgAQAEUyH0VS3HJBgHDlZdWZl02=",
"@odata.type": "#microsoft.graph.itemAttachment",
"item@odata.navigationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')",
"item@odata.associationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')/$ref",
"isInline": false,
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
"name": "Purpose of life part 2",
"size": 11840,
"item": {
"id": "",
"@odata.type": "#microsoft.graph.message",
"createdDateTime": "2024-02-05T09:33:24Z",
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
"attachments": [
{
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAACEgAQAEUyH0VS3HJBgHDlZdWZl0kSABAAjBhd4-oQaUS969pTkS-gzA==",
"@odata.type": "#microsoft.graph.fileAttachment",
"@odata.mediaContentType": "text/calendar",
"contentType": "text/calendar",
"isInline": false,
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
"name": "Abidjan.ics",
"size": 573,
"contentBytes": "QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vdHp1cmwub3JnLy9OT05TR01MIE9sc29uIDIwMjNkLy9FTg0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBZnJpY2EvQWJpZGphbg0KTEFTVC1NT0RJRklFRDoyMDIzMTIyMlQyMzMzNThaDQpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8vQWZyaWNhL0FiaWRqYW4NClgtTElDLUxPQ0FUSU9OOkFmcmljYS9BYmlkamFuDQpYLVBST0xFUFRJQy1UWk5BTUU6TE1UDQpCRUdJTjpTVEFOREFSRA0KVFpOQU1FOkdNVA0KVFpPRkZTRVRGUk9NOi0wMDE2MDgNClRaT0ZGU0VUVE86KzAwMDANCkRUU1RBUlQ6MTkxMjAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkVORDpWVElNRVpPTkUNCkVORDpWQ0FMRU5EQVINCg=="
}
],
"body": {
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none;\"> P {margin-top:0;margin-bottom:0;} </style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family: Aptos, Aptos_EmbeddedFont, Aptos_MSFontService, Calibri, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);\">I just realized the purpose of my life is to be a test case. Good to know.<br></div></body></html>",
"contentType": "html"
},
"bodyPreview": "I just realized the purpose of my life is to be a test case. Good to know.",
"conversationId": "AAQkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNAAQAFEnxDqYmbJEm8d2l3qfS6A=",
"conversationIndex": "AQHaWBYiUSfEOpiZskSbx3aXep9LoA==",
"flag": {
"flagStatus": "notFlagged"
},
"from": {
"emailAddress": {
"address": "JohannaL@10rqc2.onmicrosoft.com",
"name": "Johanna Lorenz"
}
},
"hasAttachments": true,
"importance": "normal",
"internetMessageId": "<SJ0PR04MB7294108E381BCCE5C207B6DEBC472@SJ0PR04MB7294.namprd04.prod.outlook.com>",
"isDeliveryReceiptRequested": false,
"isDraft": false,
"isRead": true,
"isReadReceiptRequested": false,
"receivedDateTime": "2024-02-05T09:33:12Z",
"sender": {
"emailAddress": {
"address": "JohannaL@10rqc2.onmicrosoft.com",
"name": "Johanna Lorenz"
}
},
"sentDateTime": "2024-02-05T09:33:11Z",
"subject": "Purpose of life",
"toRecipients": [
{
"emailAddress": {
"address": "PradeepG@10rqc2.onmicrosoft.com",
"name": "Pradeep Gupta"
}
}
],
"webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl02%3D&exvsurl=1&viewmodel=ItemAttachment"
}
},
{
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAABEgAQAEUyH0VS3HJBgHDlZdWZl03=",
"@odata.type": "#microsoft.graph.itemAttachment",
"item@odata.navigationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')",
"item@odata.associationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')/$ref",
"isInline": false,
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
"name": "Purpose of life part 3",
"size": 11840,
"item": {
"id": "",
"@odata.type": "#microsoft.graph.message",
"createdDateTime": "2024-02-05T09:33:24Z",
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
"attachments": [
{
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAACEgAQAEUyH0VS3HJBgHDlZdWZl0kSABAAjBhd4-oQaUS969pTkS-gzA==",
"@odata.type": "#microsoft.graph.fileAttachment",
"@odata.mediaContentType": "text/calendar",
"contentType": "text/calendar",
"isInline": false,
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
"name": "Abidjan.ics",
"size": 573,
"contentBytes": "QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vdHp1cmwub3JnLy9OT05TR01MIE9sc29uIDIwMjNkLy9FTg0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBZnJpY2EvQWJpZGphbg0KTEFTVC1NT0RJRklFRDoyMDIzMTIyMlQyMzMzNThaDQpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8vQWZyaWNhL0FiaWRqYW4NClgtTElDLUxPQ0FUSU9OOkFmcmljYS9BYmlkamFuDQpYLVBST0xFUFRJQy1UWk5BTUU6TE1UDQpCRUdJTjpTVEFOREFSRA0KVFpOQU1FOkdNVA0KVFpPRkZTRVRGUk9NOi0wMDE2MDgNClRaT0ZGU0VUVE86KzAwMDANCkRUU1RBUlQ6MTkxMjAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkVORDpWVElNRVpPTkUNCkVORDpWQ0FMRU5EQVINCg=="
}
],
"body": {
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none;\"> P {margin-top:0;margin-bottom:0;} </style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family: Aptos, Aptos_EmbeddedFont, Aptos_MSFontService, Calibri, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);\">I just realized the purpose of my life is to be a test case. Good to know.<br></div></body></html>",
"contentType": "html"
},
"bodyPreview": "I just realized the purpose of my life is to be a test case. Good to know.",
"conversationId": "AAQkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNAAQAFEnxDqYmbJEm8d2l3qfS6A=",
"conversationIndex": "AQHaWBYiUSfEOpiZskSbx3aXep9LoA==",
"flag": {
"flagStatus": "notFlagged"
},
"from": {
"emailAddress": {
"address": "JohannaL@10rqc2.onmicrosoft.com",
"name": "Johanna Lorenz"
}
},
"hasAttachments": true,
"importance": "normal",
"internetMessageId": "<SJ0PR04MB7294108E381BCCE5C207B6DEBC472@SJ0PR04MB7294.namprd04.prod.outlook.com>",
"isDeliveryReceiptRequested": false,
"isDraft": false,
"isRead": true,
"isReadReceiptRequested": false,
"receivedDateTime": "2024-02-05T09:33:12Z",
"sender": {
"emailAddress": {
"address": "JohannaL@10rqc2.onmicrosoft.com",
"name": "Johanna Lorenz"
}
},
"sentDateTime": "2024-02-05T09:33:11Z",
"subject": "Purpose of life",
"toRecipients": [
{
"emailAddress": {
"address": "PradeepG@10rqc2.onmicrosoft.com",
"name": "Pradeep Gupta"
}
}
],
"webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl03%3D&exvsurl=1&viewmodel=ItemAttachment"
}
} }
], ],
"bccRecipients": [], "bccRecipients": [],

View File

@ -166,3 +166,20 @@ var GraphTimeZoneToTZ = map[string]string{
"Yukon Standard Time": "America/Whitehorse", "Yukon Standard Time": "America/Whitehorse",
"tzone://Microsoft/Utc": "Etc/UTC", "tzone://Microsoft/Utc": "Etc/UTC",
} }
// Map from alternatives to the canonical time zone name
// There mapping are currently generated by manually going on the
// values in the GraphTimeZoneToTZ which is not available in the tzdb
var CanonicalTimeZoneMap = map[string]string{
"Africa/Asmara": "Africa/Asmera",
"Asia/Calcutta": "Asia/Kolkata",
"Asia/Rangoon": "Asia/Yangon",
"Asia/Saigon": "Asia/Ho_Chi_Minh",
"Europe/Kiev": "Europe/Kyiv",
"Europe/Warsaw": "Europe/Warszawa",
"America/Buenos_Aires": "America/Argentina/Buenos_Aires",
"America/Godthab": "America/Nuuk",
// NOTE: "Atlantic/Raykjavik" missing in tzdb but is in MS list
"Etc/UTC": "UTC", // simplifying the time zone name
}

View File

@ -17,6 +17,7 @@ import (
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/common/str" "github.com/alcionai/corso/src/internal/common/str"
"github.com/alcionai/corso/src/internal/converters/ics/tzdata"
"github.com/alcionai/corso/src/pkg/dttm" "github.com/alcionai/corso/src/pkg/dttm"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
@ -32,8 +33,9 @@ import (
// TODO locations: https://github.com/alcionai/corso/issues/5003 // TODO locations: https://github.com/alcionai/corso/issues/5003
const ( const (
ICalDateTimeFormat = "20060102T150405Z" ICalDateTimeFormat = "20060102T150405"
ICalDateFormat = "20060102" ICalDateTimeFormatUTC = "20060102T150405Z"
ICalDateFormat = "20060102"
) )
func keyValues(key, value string) *ics.KeyValues { func keyValues(key, value string) *ics.KeyValues {
@ -173,6 +175,17 @@ func getRecurrencePattern(
recurComponents = append(recurComponents, "BYDAY="+prefix+strings.Join(dowComponents, ",")) recurComponents = append(recurComponents, "BYDAY="+prefix+strings.Join(dowComponents, ","))
} }
// This is necessary to compute when weekly events recur
fdow := pat.GetFirstDayOfWeek()
if fdow != nil {
icalday, ok := GraphToICalDOW[fdow.String()]
if !ok {
return "", clues.NewWC(ctx, "unknown first day of week").With("day", fdow)
}
recurComponents = append(recurComponents, "WKST="+icalday)
}
rrange := recurrence.GetRangeEscaped() rrange := recurrence.GetRangeEscaped()
if rrange != nil { if rrange != nil {
switch ptr.Val(rrange.GetTypeEscaped()) { switch ptr.Val(rrange.GetTypeEscaped()) {
@ -196,7 +209,7 @@ func getRecurrencePattern(
return "", clues.WrapWC(ctx, err, "parsing end time") return "", clues.WrapWC(ctx, err, "parsing end time")
} }
recurComponents = append(recurComponents, "UNTIL="+endTime.Format(ICalDateTimeFormat)) recurComponents = append(recurComponents, "UNTIL="+endTime.Format(ICalDateTimeFormatUTC))
} }
case models.NOEND_RECURRENCERANGETYPE: case models.NOEND_RECURRENCERANGETYPE:
// Nothing to do // Nothing to do
@ -225,10 +238,15 @@ func FromEventable(ctx context.Context, event models.Eventable) (string, error)
cal := ics.NewCalendar() cal := ics.NewCalendar()
cal.SetProductId("-//Alcion//Corso") // Does this have to be customizable? cal.SetProductId("-//Alcion//Corso") // Does this have to be customizable?
err := addTimeZoneComponents(ctx, cal, event)
if err != nil {
return "", clues.Wrap(err, "adding timezone components")
}
id := ptr.Val(event.GetId()) id := ptr.Val(event.GetId())
iCalEvent := cal.AddEvent(id) iCalEvent := cal.AddEvent(id)
err := updateEventProperties(ctx, event, iCalEvent) err = updateEventProperties(ctx, event, iCalEvent)
if err != nil { if err != nil {
return "", clues.Wrap(err, "updating event properties") return "", clues.Wrap(err, "updating event properties")
} }
@ -259,7 +277,7 @@ func FromEventable(ctx context.Context, event models.Eventable) (string, error)
exICalEvent := cal.AddEvent(id) exICalEvent := cal.AddEvent(id)
start := exception.GetOriginalStart() // will always be in UTC start := exception.GetOriginalStart() // will always be in UTC
exICalEvent.AddProperty(ics.ComponentProperty(ics.PropertyRecurrenceId), start.Format(ICalDateTimeFormat)) exICalEvent.AddProperty(ics.ComponentProperty(ics.PropertyRecurrenceId), start.Format(ICalDateTimeFormatUTC))
err = updateEventProperties(ctx, exception, exICalEvent) err = updateEventProperties(ctx, exception, exICalEvent)
if err != nil { if err != nil {
@ -270,6 +288,91 @@ func FromEventable(ctx context.Context, event models.Eventable) (string, error)
return cal.Serialize(), nil return cal.Serialize(), nil
} }
func getTZDataKeyValues(ctx context.Context, timezone string) (map[string]string, error) {
template, ok := tzdata.TZData[timezone]
if !ok {
return nil, clues.NewWC(ctx, "timezone not found in tz database").
With("timezone", timezone)
}
keyValues := map[string]string{}
for _, line := range strings.Split(template, "\n") {
splits := strings.SplitN(line, ":", 2)
if len(splits) != 2 {
return nil, clues.NewWC(ctx, "invalid tzdata line").
With("line", line).
With("timezone", timezone)
}
keyValues[splits[0]] = splits[1]
}
return keyValues, nil
}
func addTimeZoneComponents(ctx context.Context, cal *ics.Calendar, event models.Eventable) error {
// Handling of timezone get a bit tricky when we have to deal with
// relative recurrence. The issue comes up when we set a recurrence
// to be something like "repeat every 3rd Tuesday". Tuesday in UTC
// and in IST will be different and so we cannot just always use UTC.
//
// The way this is solved is by using the timezone in the
// recurrence for start and end timezones as we have to use UTC
// for UNTIL(mostly).
// https://www.rfc-editor.org/rfc/rfc5545#section-3.3.10
timezone, err := getRecurrenceTimezone(ctx, event)
if err != nil {
return clues.Stack(err)
}
if timezone != time.UTC {
kvs, err := getTZDataKeyValues(ctx, timezone.String())
if err != nil {
return clues.Stack(err)
}
tz := cal.AddTimezone(timezone.String())
for k, v := range kvs {
tz.AddProperty(ics.ComponentProperty(k), v)
}
}
return nil
}
// getRecurrenceTimezone get the timezone specified by the recurrence
// in the calendar. It does a normalization pass where we always convert
// the timezone to the value in tzdb If we don't have a recurrence
// timezone, we don't have to use a specific timezone in the export and
// is safe to return UTC from this method.
func getRecurrenceTimezone(ctx context.Context, event models.Eventable) (*time.Location, error) {
if event.GetRecurrence() != nil {
timezone := ptr.Val(event.GetRecurrence().GetRangeEscaped().GetRecurrenceTimeZone())
ctz, ok := GraphTimeZoneToTZ[timezone]
if ok {
timezone = ctz
}
cannon, ok := CanonicalTimeZoneMap[timezone]
if ok {
timezone = cannon
}
loc, err := time.LoadLocation(timezone)
if err != nil {
return nil, clues.WrapWC(ctx, err, "unknown timezone").
With("timezone", timezone)
}
return loc, nil
}
return time.UTC, nil
}
func isASCII(s string) bool { func isASCII(s string) bool {
for _, c := range s { for _, c := range s {
if c > unicode.MaxASCII { if c > unicode.MaxASCII {
@ -299,6 +402,11 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
iCalEvent.SetModifiedAt(ptr.Val(modified)) iCalEvent.SetModifiedAt(ptr.Val(modified))
} }
timezone, err := getRecurrenceTimezone(ctx, event)
if err != nil {
return err
}
// DTSTART - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.4 // DTSTART - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.4
allDay := ptr.Val(event.GetIsAllDay()) allDay := ptr.Val(event.GetIsAllDay())
startString := event.GetStart().GetDateTime() startString := event.GetStart().GetDateTime()
@ -310,11 +418,7 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
return clues.WrapWC(ctx, err, "parsing start time") return clues.WrapWC(ctx, err, "parsing start time")
} }
if allDay { addTime(iCalEvent, ics.ComponentPropertyDtStart, start, allDay, timezone)
iCalEvent.SetStartAt(start, ics.WithValue(string(ics.ValueDataTypeDate)))
} else {
iCalEvent.SetStartAt(start)
}
} }
// DTEND - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.2 // DTEND - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.2
@ -327,11 +431,7 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
return clues.WrapWC(ctx, err, "parsing end time") return clues.WrapWC(ctx, err, "parsing end time")
} }
if allDay { addTime(iCalEvent, ics.ComponentPropertyDtEnd, end, allDay, timezone)
iCalEvent.SetEndAt(end, ics.WithValue(string(ics.ValueDataTypeDate)))
} else {
iCalEvent.SetEndAt(end)
}
} }
recurrence := event.GetRecurrence() recurrence := event.GetRecurrence()
@ -384,7 +484,14 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
desc := replacer.Replace(description) desc := replacer.Replace(description)
iCalEvent.AddProperty("X-ALT-DESC", desc, ics.WithFmtType("text/html")) iCalEvent.AddProperty("X-ALT-DESC", desc, ics.WithFmtType("text/html"))
} else { } else {
stripped, err := html2text.FromString(description, html2text.Options{PrettyTables: true}) // Disable auto wrap, causes huge memory spikes
// https://github.com/jaytaylor/html2text/issues/48
prettyTablesOptions := html2text.NewPrettyTablesOptions()
prettyTablesOptions.AutoWrapText = false
stripped, err := html2text.FromString(
description,
html2text.Options{PrettyTables: true, PrettyTablesOptions: prettyTablesOptions})
if err != nil { if err != nil {
return clues.Wrap(err, "converting html to text"). return clues.Wrap(err, "converting html to text").
With("description_length", len(description)) With("description_length", len(description))
@ -630,6 +737,26 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
return nil return nil
} }
func addTime(iCalEvent *ics.VEvent, prop ics.ComponentProperty, tm time.Time, allDay bool, tzLoc *time.Location) {
if allDay {
if tzLoc == time.UTC {
iCalEvent.SetProperty(prop, tm.Format(ICalDateFormat), ics.WithValue(string(ics.ValueDataTypeDate)))
} else {
iCalEvent.SetProperty(
prop,
tm.In(tzLoc).Format(ICalDateFormat),
ics.WithValue(string(ics.ValueDataTypeDate)),
keyValues("TZID", tzLoc.String()))
}
} else {
if tzLoc == time.UTC {
iCalEvent.SetProperty(prop, tm.Format(ICalDateTimeFormatUTC))
} else {
iCalEvent.SetProperty(prop, tm.In(tzLoc).Format(ICalDateTimeFormat), keyValues("TZID", tzLoc.String()))
}
}
}
func getCancelledDates(ctx context.Context, event models.Eventable) ([]time.Time, error) { func getCancelledDates(ctx context.Context, event models.Eventable) ([]time.Time, error) {
dateStrings, err := api.GetCancelledEventDateStrings(event) dateStrings, err := api.GetCancelledEventDateStrings(event)
if err != nil { if err != nil {

View File

@ -13,6 +13,7 @@ import (
"testing" "testing"
"time" "time"
ics "github.com/arran4/golang-ical"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
kjson "github.com/microsoft/kiota-serialization-json-go" kjson "github.com/microsoft/kiota-serialization-json-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
@ -21,6 +22,7 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/converters/ics/tzdata"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
) )
@ -32,7 +34,7 @@ func TestICSUnitSuite(t *testing.T) {
suite.Run(t, &ICSUnitSuite{Suite: tester.NewUnitSuite(t)}) suite.Run(t, &ICSUnitSuite{Suite: tester.NewUnitSuite(t)})
} }
func (suite *ICSUnitSuite) TestGetLocationString() { func (s *ICSUnitSuite) TestGetLocationString() {
table := []struct { table := []struct {
name string name string
loc func() models.Locationable loc func() models.Locationable
@ -110,13 +112,13 @@ func (suite *ICSUnitSuite) TestGetLocationString() {
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
assert.Equal(suite.T(), tt.expect, getLocationString(tt.loc())) assert.Equal(s.T(), tt.expect, getLocationString(tt.loc()))
}) })
} }
} }
func (suite *ICSUnitSuite) TestGetUTCTime() { func (s *ICSUnitSuite) TestGetUTCTime() {
table := []struct { table := []struct {
name string name string
timestamp string timestamp string
@ -162,18 +164,18 @@ func (suite *ICSUnitSuite) TestGetUTCTime() {
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
t, err := GetUTCTime(tt.timestamp, tt.timezone) t, err := GetUTCTime(tt.timestamp, tt.timezone)
tt.errCheck(suite.T(), err) tt.errCheck(s.T(), err)
if !tt.time.Equal(time.Time{}) { if !tt.time.Equal(time.Time{}) {
assert.Equal(suite.T(), tt.time, t) assert.Equal(s.T(), tt.time, t)
} }
}) })
} }
} }
func (suite *ICSUnitSuite) TestGetRecurrencePattern() { func (s *ICSUnitSuite) TestGetRecurrencePattern() {
table := []struct { table := []struct {
name string name string
recurrence func() models.PatternedRecurrenceable recurrence func() models.PatternedRecurrenceable
@ -187,16 +189,37 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("daily") typ, err := models.ParseRecurrencePatternType("daily")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rec.SetPattern(pat) rec.SetPattern(pat)
return rec return rec
}, },
expect: "FREQ=DAILY;INTERVAL=1", expect: "FREQ=DAILY;INTERVAL=1;WKST=SU",
errCheck: require.NoError,
},
{
name: "daily different start of week",
recurrence: func() models.PatternedRecurrenceable {
rec := models.NewPatternedRecurrence()
pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("daily")
require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.MONDAY_DAYOFWEEK))
rec.SetPattern(pat)
return rec
},
expect: "FREQ=DAILY;INTERVAL=1;WKST=MO",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -206,15 +229,16 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("daily") typ, err := models.ParseRecurrencePatternType("daily")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rng := models.NewRecurrenceRange() rng := models.NewRecurrenceRange()
rrtype, err := models.ParseRecurrenceRangeType("endDate") rrtype, err := models.ParseRecurrenceRangeType("endDate")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType)) rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
@ -227,7 +251,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
return rec return rec
}, },
expect: "FREQ=DAILY;INTERVAL=1;UNTIL=20210101T182959Z", expect: "FREQ=DAILY;INTERVAL=1;WKST=SU;UNTIL=20210101T182959Z",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -237,16 +261,17 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("weekly") typ, err := models.ParseRecurrencePatternType("weekly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rec.SetPattern(pat) rec.SetPattern(pat)
return rec return rec
}, },
expect: "FREQ=WEEKLY;INTERVAL=1", expect: "FREQ=WEEKLY;INTERVAL=1;WKST=SU",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -256,15 +281,16 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("weekly") typ, err := models.ParseRecurrencePatternType("weekly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rng := models.NewRecurrenceRange() rng := models.NewRecurrenceRange()
rrtype, err := models.ParseRecurrenceRangeType("endDate") rrtype, err := models.ParseRecurrenceRangeType("endDate")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType)) rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
@ -277,7 +303,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
return rec return rec
}, },
expect: "FREQ=WEEKLY;INTERVAL=1;UNTIL=20210101T235959Z", expect: "FREQ=WEEKLY;INTERVAL=1;WKST=SU;UNTIL=20210101T235959Z",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -287,15 +313,16 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("weekly") typ, err := models.ParseRecurrencePatternType("weekly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rng := models.NewRecurrenceRange() rng := models.NewRecurrenceRange()
rrtype, err := models.ParseRecurrenceRangeType("numbered") rrtype, err := models.ParseRecurrenceRangeType("numbered")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType)) rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
@ -307,7 +334,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
return rec return rec
}, },
expect: "FREQ=WEEKLY;INTERVAL=1;COUNT=10", expect: "FREQ=WEEKLY;INTERVAL=1;WKST=SU;COUNT=10",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -317,10 +344,11 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("weekly") typ, err := models.ParseRecurrencePatternType("weekly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
days := []models.DayOfWeek{ days := []models.DayOfWeek{
models.MONDAY_DAYOFWEEK, models.MONDAY_DAYOFWEEK,
@ -334,7 +362,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
return rec return rec
}, },
expect: "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,TH", expect: "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,TH;WKST=SU",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -344,16 +372,17 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("daily") typ, err := models.ParseRecurrencePatternType("daily")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(2))) pat.SetInterval(ptr.To(int32(2)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rec.SetPattern(pat) rec.SetPattern(pat)
return rec return rec
}, },
expect: "FREQ=DAILY;INTERVAL=2", expect: "FREQ=DAILY;INTERVAL=2;WKST=SU",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -363,10 +392,11 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("absoluteMonthly") typ, err := models.ParseRecurrencePatternType("absoluteMonthly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
pat.SetDayOfMonth(ptr.To(int32(5))) pat.SetDayOfMonth(ptr.To(int32(5)))
@ -374,7 +404,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
return rec return rec
}, },
expect: "FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5", expect: "FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5;WKST=SU",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -384,10 +414,11 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("absoluteYearly") typ, err := models.ParseRecurrencePatternType("absoluteYearly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(3))) pat.SetInterval(ptr.To(int32(3)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
pat.SetMonth(ptr.To(int32(8))) pat.SetMonth(ptr.To(int32(8)))
@ -395,7 +426,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
return rec return rec
}, },
expect: "FREQ=YEARLY;INTERVAL=3;BYMONTH=8", expect: "FREQ=YEARLY;INTERVAL=3;BYMONTH=8;WKST=SU",
errCheck: require.NoError, errCheck: require.NoError,
}, },
{ {
@ -405,37 +436,38 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
typ, err := models.ParseRecurrencePatternType("relativeYearly") typ, err := models.ParseRecurrencePatternType("relativeYearly")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
pat.SetMonth(ptr.To(int32(8))) pat.SetMonth(ptr.To(int32(8)))
pat.SetDaysOfWeek([]models.DayOfWeek{models.FRIDAY_DAYOFWEEK}) pat.SetDaysOfWeek([]models.DayOfWeek{models.FRIDAY_DAYOFWEEK})
wi, err := models.ParseWeekIndex("first") wi, err := models.ParseWeekIndex("first")
require.NoError(suite.T(), err) require.NoError(s.T(), err)
pat.SetIndex(wi.(*models.WeekIndex)) pat.SetIndex(wi.(*models.WeekIndex))
rec.SetPattern(pat) rec.SetPattern(pat)
return rec return rec
}, },
expect: "FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=1FR", expect: "FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=1FR;WKST=SU",
errCheck: require.NoError, errCheck: require.NoError,
}, },
// TODO(meain): could still use more tests for edge cases of time // TODO(meain): could still use more tests for edge cases of time
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
ctx, flush := tester.NewContext(suite.T()) ctx, flush := tester.NewContext(s.T())
defer flush() defer flush()
rec, err := getRecurrencePattern(ctx, tt.recurrence()) rec, err := getRecurrencePattern(ctx, tt.recurrence())
tt.errCheck(suite.T(), err) tt.errCheck(s.T(), err)
assert.Equal(suite.T(), tt.expect, rec) assert.Equal(s.T(), tt.expect, rec)
}) })
} }
} }
@ -460,8 +492,8 @@ func baseEvent() *models.Event {
return e return e
} }
func (suite *ICSUnitSuite) TestEventConversion() { func (s *ICSUnitSuite) TestEventConversion() {
t := suite.T() t := s.T()
table := []struct { table := []struct {
name string name string
@ -546,14 +578,19 @@ func (suite *ICSUnitSuite) TestEventConversion() {
rec := models.NewPatternedRecurrence() rec := models.NewPatternedRecurrence()
pat := models.NewRecurrencePattern() pat := models.NewRecurrencePattern()
rng := models.NewRecurrenceRange()
typ, err := models.ParseRecurrencePatternType("daily") typ, err := models.ParseRecurrencePatternType("daily")
require.NoError(t, err) require.NoError(t, err)
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType)) pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
pat.SetInterval(ptr.To(int32(1))) pat.SetInterval(ptr.To(int32(1)))
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
rng.SetRecurrenceTimeZone(ptr.To("UTC"))
rec.SetPattern(pat) rec.SetPattern(pat)
rec.SetRangeEscaped(rng)
e.SetRecurrence(rec) e.SetRecurrence(rec)
@ -830,8 +867,8 @@ func (suite *ICSUnitSuite) TestEventConversion() {
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
t := suite.T() t := s.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
@ -881,8 +918,8 @@ func checkAttendee(t *testing.T, out, check, msg string) {
assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg)) assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg))
} }
func (suite *ICSUnitSuite) TestAttendees() { func (s *ICSUnitSuite) TestAttendees() {
t := suite.T() t := s.T()
table := []struct { table := []struct {
name string name string
@ -949,8 +986,8 @@ func (suite *ICSUnitSuite) TestAttendees() {
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
t := suite.T() t := s.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
@ -1071,8 +1108,8 @@ func checkAttachment(t *testing.T, out, check, msg string) {
assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg)) assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg))
} }
func (suite *ICSUnitSuite) TestAttachments() { func (s *ICSUnitSuite) TestAttachments() {
t := suite.T() t := s.T()
type attachment struct { type attachment struct {
cid string // contentid cid string // contentid
@ -1128,8 +1165,8 @@ func (suite *ICSUnitSuite) TestAttachments() {
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
t := suite.T() t := s.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
@ -1172,7 +1209,7 @@ func (suite *ICSUnitSuite) TestAttachments() {
} }
} }
func (suite *ICSUnitSuite) TestCancellations() { func (s *ICSUnitSuite) TestCancellations() {
table := []struct { table := []struct {
name string name string
cancelledIds []string cancelledIds []string
@ -1196,8 +1233,8 @@ func (suite *ICSUnitSuite) TestCancellations() {
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
t := suite.T() t := s.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
@ -1260,7 +1297,7 @@ func eventToJSON(e *models.Event) ([]byte, error) {
return bts, err return bts, err
} }
func (suite *ICSUnitSuite) TestEventExceptions() { func (s *ICSUnitSuite) TestEventExceptions() {
table := []struct { table := []struct {
name string name string
event func() *models.Event event func() *models.Event
@ -1282,7 +1319,7 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
exception.SetEnd(newEnd) exception.SetEnd(newEnd)
parsed, err := eventToMap(exception) parsed, err := eventToMap(exception)
require.NoError(suite.T(), err, "parsing exception") require.NoError(s.T(), err, "parsing exception")
// add exception event to additional data // add exception event to additional data
e.SetAdditionalData(map[string]any{ e.SetAdditionalData(map[string]any{
@ -1301,15 +1338,15 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
} }
} }
assert.Equal(suite.T(), 2, events, "number of events") assert.Equal(s.T(), 2, events, "number of events")
assert.Contains(suite.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id") assert.Contains(s.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id")
assert.Contains(suite.T(), out, "SUMMARY:Subject", "original event") assert.Contains(s.T(), out, "SUMMARY:Subject", "original event")
assert.Contains(suite.T(), out, "SUMMARY:Exception", "exception event") assert.Contains(s.T(), out, "SUMMARY:Exception", "exception event")
assert.Contains(suite.T(), out, "DTSTART:20210101T130000Z", "new start time") assert.Contains(s.T(), out, "DTSTART:20210101T130000Z", "new start time")
assert.Contains(suite.T(), out, "DTEND:20210101T140000Z", "new end time") assert.Contains(s.T(), out, "DTEND:20210101T140000Z", "new end time")
}, },
}, },
{ {
@ -1338,10 +1375,10 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
exception2.SetEnd(newEnd) exception2.SetEnd(newEnd)
parsed1, err := eventToMap(exception1) parsed1, err := eventToMap(exception1)
require.NoError(suite.T(), err, "parsing exception 1") require.NoError(s.T(), err, "parsing exception 1")
parsed2, err := eventToMap(exception2) parsed2, err := eventToMap(exception2)
require.NoError(suite.T(), err, "parsing exception 2") require.NoError(s.T(), err, "parsing exception 2")
// add exception event to additional data // add exception event to additional data
e.SetAdditionalData(map[string]any{ e.SetAdditionalData(map[string]any{
@ -1360,36 +1397,230 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
} }
} }
assert.Equal(suite.T(), 3, events, "number of events") assert.Equal(s.T(), 3, events, "number of events")
assert.Contains(suite.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id 1") assert.Contains(s.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id 1")
assert.Contains(suite.T(), out, "RECURRENCE-ID:20210102T120000Z", "recurrence id 2") assert.Contains(s.T(), out, "RECURRENCE-ID:20210102T120000Z", "recurrence id 2")
assert.Contains(suite.T(), out, "SUMMARY:Subject", "original event") assert.Contains(s.T(), out, "SUMMARY:Subject", "original event")
assert.Contains(suite.T(), out, "SUMMARY:Exception 1", "exception event 1") assert.Contains(s.T(), out, "SUMMARY:Exception 1", "exception event 1")
assert.Contains(suite.T(), out, "SUMMARY:Exception 2", "exception event 2") assert.Contains(s.T(), out, "SUMMARY:Exception 2", "exception event 2")
assert.Contains(suite.T(), out, "DTSTART:20210101T130000Z", "new start time 1") assert.Contains(s.T(), out, "DTSTART:20210101T130000Z", "new start time 1")
assert.Contains(suite.T(), out, "DTEND:20210101T140000Z", "new end time 1") assert.Contains(s.T(), out, "DTEND:20210101T140000Z", "new end time 1")
assert.Contains(suite.T(), out, "DTSTART:20210102T130000Z", "new start time 2") assert.Contains(s.T(), out, "DTSTART:20210102T130000Z", "new start time 2")
assert.Contains(suite.T(), out, "DTEND:20210102T140000Z", "new end time 2") assert.Contains(s.T(), out, "DTEND:20210102T140000Z", "new end time 2")
}, },
}, },
} }
for _, tt := range table { for _, tt := range table {
suite.Run(tt.name, func() { s.Run(tt.name, func() {
ctx, flush := tester.NewContext(suite.T()) ctx, flush := tester.NewContext(s.T())
defer flush() defer flush()
bts, err := eventToJSON(tt.event()) bts, err := eventToJSON(tt.event())
require.NoError(suite.T(), err, "getting serialized content") require.NoError(s.T(), err, "getting serialized content")
out, err := FromJSON(ctx, bts) out, err := FromJSON(ctx, bts)
require.NoError(suite.T(), err, "converting to ics") require.NoError(s.T(), err, "converting to ics")
tt.check(out) tt.check(out)
}) })
} }
} }
func (s *ICSUnitSuite) TestGetRecurrenceTimezone() {
table := []struct {
name string
intz string
outtz string
}{
{
name: "empty",
intz: "",
outtz: "UTC",
},
{
name: "utc",
intz: "UTC",
outtz: "UTC",
},
{
name: "simple",
intz: "Asia/Kolkata",
outtz: "Asia/Kolkata",
},
{
name: "windows tz",
intz: "India Standard Time",
outtz: "Asia/Kolkata",
},
{
name: "non canonical",
intz: "Asia/Calcutta",
outtz: "Asia/Kolkata",
},
}
for _, tt := range table {
s.Run(tt.name, func() {
ctx, flush := tester.NewContext(s.T())
defer flush()
event := baseEvent()
if len(tt.intz) > 0 {
recur := models.NewPatternedRecurrence()
rp := models.NewRecurrenceRange()
rp.SetRecurrenceTimeZone(ptr.To(tt.intz))
recur.SetRangeEscaped(rp)
event.SetRecurrence(recur)
}
timezone, err := getRecurrenceTimezone(ctx, event)
require.NoError(s.T(), err)
assert.Equal(s.T(), tt.outtz, timezone.String())
})
}
}
func (s *ICSUnitSuite) TestAddTimezoneComponents() {
event := baseEvent()
recur := models.NewPatternedRecurrence()
rp := models.NewRecurrenceRange()
rp.SetRecurrenceTimeZone(ptr.To("Asia/Kolkata"))
recur.SetRangeEscaped(rp)
event.SetRecurrence(recur)
ctx, flush := tester.NewContext(s.T())
defer flush()
cal := ics.NewCalendar()
err := addTimeZoneComponents(ctx, cal, event)
require.NoError(s.T(), err)
text := cal.Serialize()
assert.Contains(s.T(), text, "BEGIN:VTIMEZONE", "beginning of timezone")
assert.Contains(s.T(), text, "TZID:Asia/Kolkata", "timezone id")
assert.Contains(s.T(), text, "END:VTIMEZONE", "end of timezone")
}
func (s *ICSUnitSuite) TestAddTime() {
locak, err := time.LoadLocation("Asia/Kolkata")
require.NoError(s.T(), err)
table := []struct {
name string
prop ics.ComponentProperty
time time.Time
allDay bool
loc *time.Location
exp string
}{
{
name: "utc",
prop: ics.ComponentPropertyDtStart,
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
allDay: false,
loc: time.UTC,
exp: "DTSTART:20210102T030405Z",
},
{
name: "local",
prop: ics.ComponentPropertyDtStart,
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
allDay: false,
loc: locak,
exp: "DTSTART;TZID=Asia/Kolkata:20210102T083405",
},
{
name: "all day",
prop: ics.ComponentPropertyDtStart,
time: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC),
allDay: true,
loc: time.UTC,
exp: "DTSTART;VALUE=DATE:20210102",
},
{
name: "all day local",
prop: ics.ComponentPropertyDtStart,
time: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC),
allDay: true,
loc: locak,
exp: "DTSTART;VALUE=DATE;TZID=Asia/Kolkata:20210102",
},
{
name: "end",
prop: ics.ComponentPropertyDtEnd,
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
allDay: false,
loc: time.UTC,
exp: "DTEND:20210102T030405Z",
},
{
// This won't happen, but a good test to have to test loc handling
name: "windows tz",
prop: ics.ComponentPropertyDtStart,
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
allDay: false,
loc: time.FixedZone("India Standard Time", 5*60*60+30*60),
exp: "DTSTART;TZID=India Standard Time:20210102T083405",
},
}
for _, tt := range table {
s.Run(tt.name, func() {
cal := ics.NewCalendar()
evt := cal.AddEvent("id")
addTime(evt, tt.prop, tt.time, tt.allDay, tt.loc)
expSplits := strings.FieldsFunc(tt.exp, func(c rune) bool {
return c == ':' || c == ';'
})
text := cal.Serialize()
checkLine := ""
for _, l := range strings.Split(text, "\r\n") {
if strings.HasPrefix(l, string(tt.prop)) {
checkLine = l
break
}
}
actSplits := strings.FieldsFunc(checkLine, func(c rune) bool {
return c == ':' || c == ';'
})
assert.Greater(s.T(), len(checkLine), 0, "line not found")
assert.Equal(s.T(), len(expSplits), len(actSplits), "length of fields")
assert.ElementsMatch(s.T(), expSplits, actSplits, "fields")
})
}
}
// This tests and ensures that the generated data is int he format
// that we expect
func (s *ICSUnitSuite) TestGetTZDataKeyValues() {
for key := range tzdata.TZData {
s.Run(key, func() {
ctx, flush := tester.NewContext(s.T())
defer flush()
data, err := getTZDataKeyValues(ctx, key)
require.NoError(s.T(), err)
assert.NotEmpty(s.T(), data, "data")
assert.NotContains(s.T(), data, "BEGIN", "beginning of timezone") // should be stripped
assert.NotContains(s.T(), data, "END", "end of timezone") // should be stripped
assert.NotContains(s.T(), data, "TZID", "timezone id") // should be stripped
assert.Contains(s.T(), data, "DTSTART", "start time")
assert.Contains(s.T(), data, "TZOFFSETFROM", "offset from")
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
#!/bin/sh
set -eo pipefail
if ! echo "$PWD" | grep -q '/tzdata$'; then
echo "Please run this script from the tzdata dir"
exit 1
fi
# TODO: Generate from https://www.iana.org/time-zones
if [ ! -d /tmp/corso-tzdata ]; then
git clone --depth 1 https://github.com/add2cal/timezones-ical-library.git /tmp/corso-tzdata
else
cd /tmp/corso-tzdata
git pull
cd -
fi
# Generate a huge go file with all the timezones
echo "package tzdata" >data.go
echo "" >>data.go
echo "var TZData = map[string]string{" >>data.go
find /tmp/corso-tzdata/ -name '*.ics' | while read -r f; do
tz=$(echo "$f" | sed 's|/tmp/corso-tzdata/api/||;s|\.ics$||')
echo "Processing $tz"
printf "\t\"%s\": \`" "$tz" >>data.go
cat "$f" | grep -Ev "(BEGIN:|END:|TZID:)" |
sed 's|`|\\`|g;s|\r||;s|TZID:/timezones-ical-library/|TZID:|' |
perl -pe 'chomp if eof' >>data.go
echo "\`," >>data.go
done
echo "}" >>data.go

View File

@ -59,6 +59,15 @@ const (
minEpochDurationUpperBound = 7 * 24 * time.Hour minEpochDurationUpperBound = 7 * 24 * time.Hour
) )
// allValidCompressors is the set of compression algorithms either currently
// being used or that were previously used. Use this during the config verify
// command to avoid spurious errors. We can revisit whether we want to update
// the config in those old repos at a later time.
var allValidCompressors = map[compression.Name]struct{}{
compression.Name(defaultCompressor): {},
compression.Name("s2-default"): {},
}
var ( var (
ErrSettingDefaultConfig = clues.New("setting default repo config values") ErrSettingDefaultConfig = clues.New("setting default repo config values")
ErrorRepoAlreadyExists = clues.New("repo already exists") ErrorRepoAlreadyExists = clues.New("repo already exists")
@ -768,7 +777,7 @@ func (w *conn) verifyDefaultPolicyConfigOptions(
ctx = clues.Add(ctx, "current_global_policy", globalPol.String()) ctx = clues.Add(ctx, "current_global_policy", globalPol.String())
if globalPol.CompressionPolicy.CompressorName != defaultCompressor { if _, ok := allValidCompressors[globalPol.CompressionPolicy.CompressorName]; !ok {
errs.AddAlert(ctx, fault.NewAlert( errs.AddAlert(ctx, fault.NewAlert(
"unexpected compressor", "unexpected compressor",
corsoWrapperAlertNamespace, corsoWrapperAlertNamespace,

View File

@ -891,6 +891,20 @@ func (suite *ConnRetentionIntegrationSuite) TestVerifyDefaultConfigOptions() {
}, },
expectAlerts: 1, expectAlerts: 1,
}, },
{
name: "OldValidCompressor",
setupRepo: func(ctx context.Context, t *testing.T, con *conn) {
pol, err := con.getGlobalPolicyOrEmpty(ctx)
require.NoError(t, err, clues.ToCore(err))
_, err = updateCompressionOnPolicy("s2-default", pol)
require.NoError(t, err, clues.ToCore(err))
err = con.writeGlobalPolicy(ctx, "test", pol)
require.NoError(t, err, clues.ToCore(err))
},
expectAlerts: 0,
},
{ {
name: "NonDefaultCompression", name: "NonDefaultCompression",
setupRepo: func(ctx context.Context, t *testing.T, con *conn) { setupRepo: func(ctx context.Context, t *testing.T, con *conn) {

View File

@ -366,7 +366,7 @@ func downloadContent(
itemID := ptr.Val(item.GetId()) itemID := ptr.Val(item.GetId())
ctx = clues.Add(ctx, "item_id", itemID) ctx = clues.Add(ctx, "item_id", itemID)
content, err := downloadItem(ctx, iaag, item) content, err := downloadItem(ctx, iaag, driveID, item)
if err == nil { if err == nil {
return content, nil return content, nil
} else if !graph.IsErrUnauthorizedOrBadToken(err) { } else if !graph.IsErrUnauthorizedOrBadToken(err) {
@ -395,7 +395,7 @@ func downloadContent(
cdi := custom.ToCustomDriveItem(di) cdi := custom.ToCustomDriveItem(di)
content, err = downloadItem(ctx, iaag, cdi) content, err = downloadItem(ctx, iaag, driveID, cdi)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "content download retry") return nil, clues.Wrap(err, "content download retry")
} }
@ -426,7 +426,7 @@ func readItemContents(
return nil, core.ErrNotFound return nil, core.ErrNotFound
} }
rc, err := downloadFile(ctx, iaag, props.downloadURL) rc, err := downloadFile(ctx, iaag, props.downloadURL, false)
if graph.IsErrUnauthorizedOrBadToken(err) { if graph.IsErrUnauthorizedOrBadToken(err) {
logger.CtxErr(ctx, err).Debug("stale item in cache") logger.CtxErr(ctx, err).Debug("stale item in cache")
} }

View File

@ -795,7 +795,12 @@ func (h mockBackupHandler[T]) AugmentItemInfo(
return h.ItemInfo return h.ItemInfo
} }
func (h *mockBackupHandler[T]) Get(context.Context, string, map[string]string) (*http.Response, error) { func (h *mockBackupHandler[T]) Get(
context.Context,
string,
map[string]string,
bool,
) (*http.Response, error) {
c := h.getCall c := h.getCall
h.getCall++ h.getCall++

View File

@ -21,8 +21,10 @@ import (
) )
const ( const (
acceptHeaderKey = "Accept" acceptHeaderKey = "Accept"
acceptHeaderValue = "*/*" acceptHeaderValue = "*/*"
gigabyte = 1024 * 1024 * 1024
largeFileDownloadLimit = 15 * gigabyte
) )
// downloadUrlKeys is used to find the download URL in a DriveItem response. // downloadUrlKeys is used to find the download URL in a DriveItem response.
@ -33,7 +35,8 @@ var downloadURLKeys = []string{
func downloadItem( func downloadItem(
ctx context.Context, ctx context.Context,
ag api.Getter, getter api.Getter,
driveID string,
item *custom.DriveItem, item *custom.DriveItem,
) (io.ReadCloser, error) { ) (io.ReadCloser, error) {
if item == nil { if item == nil {
@ -41,36 +44,37 @@ func downloadItem(
} }
var ( var (
rc io.ReadCloser // very large file content needs to be downloaded through a different endpoint, or else
isFile = item.GetFile() != nil // the download could take longer than the lifespan of the download token in the cached
err error // url, which will cause us to timeout on every download request, even if we refresh the
// download url right before the query.
url = "https://graph.microsoft.com/v1.0/drives/" + driveID + "/items/" + ptr.Val(item.GetId()) + "/content"
reader io.ReadCloser
err error
isLargeFile = ptr.Val(item.GetSize()) > largeFileDownloadLimit
) )
if isFile { // if this isn't a file, no content is available for download
var ( if item.GetFile() == nil {
url string return reader, nil
ad = item.GetAdditionalData()
)
for _, key := range downloadURLKeys {
if v, err := str.AnyValueToString(key, ad); err == nil {
url = v
break
}
}
rc, err = downloadFile(ctx, ag, url)
if err != nil {
return nil, clues.Stack(err)
}
} }
return rc, nil // smaller files will maintain our current behavior (prefetching the download url with the
// url cache). That pattern works for us in general, and we only need to deviate for very
// large file sizes.
if !isLargeFile {
url = str.FirstIn(item.GetAdditionalData(), downloadURLKeys...)
}
reader, err = downloadFile(ctx, getter, url, isLargeFile)
return reader, clues.StackWC(ctx, err).OrNil()
} }
type downloadWithRetries struct { type downloadWithRetries struct {
getter api.Getter getter api.Getter
url string requireAuth bool
url string
} }
func (dg *downloadWithRetries) SupportsRange() bool { func (dg *downloadWithRetries) SupportsRange() bool {
@ -86,7 +90,7 @@ func (dg *downloadWithRetries) Get(
// wouldn't work without it (get 416 responses instead of 206). // wouldn't work without it (get 416 responses instead of 206).
headers[acceptHeaderKey] = acceptHeaderValue headers[acceptHeaderKey] = acceptHeaderValue
resp, err := dg.getter.Get(ctx, dg.url, headers) resp, err := dg.getter.Get(ctx, dg.url, headers, dg.requireAuth)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "getting file") return nil, clues.Wrap(err, "getting file")
} }
@ -96,7 +100,7 @@ func (dg *downloadWithRetries) Get(
resp.Body.Close() resp.Body.Close()
} }
return nil, clues.New("malware detected").Label(graph.LabelsMalware) return nil, clues.NewWC(ctx, "malware detected").Label(graph.LabelsMalware)
} }
if resp != nil && (resp.StatusCode/100) != 2 { if resp != nil && (resp.StatusCode/100) != 2 {
@ -107,7 +111,7 @@ func (dg *downloadWithRetries) Get(
// upstream error checks can compare the status with // upstream error checks can compare the status with
// clues.HasLabel(err, graph.LabelStatus(http.KnownStatusCode)) // clues.HasLabel(err, graph.LabelStatus(http.KnownStatusCode))
return nil, clues. return nil, clues.
Wrap(clues.New(resp.Status), "non-2xx http response"). Wrap(clues.NewWC(ctx, resp.Status), "non-2xx http response").
Label(graph.LabelStatus(resp.StatusCode)) Label(graph.LabelStatus(resp.StatusCode))
} }
@ -118,6 +122,7 @@ func downloadFile(
ctx context.Context, ctx context.Context,
ag api.Getter, ag api.Getter,
url string, url string,
requireAuth bool,
) (io.ReadCloser, error) { ) (io.ReadCloser, error) {
if len(url) == 0 { if len(url) == 0 {
return nil, clues.NewWC(ctx, "empty file url") return nil, clues.NewWC(ctx, "empty file url")
@ -141,8 +146,9 @@ func downloadFile(
rc, err := readers.NewResetRetryHandler( rc, err := readers.NewResetRetryHandler(
ctx, ctx,
&downloadWithRetries{ &downloadWithRetries{
getter: ag, getter: ag,
url: url, requireAuth: requireAuth,
url: url,
}) })
return rc, clues.Stack(err).OrNil() return rc, clues.Stack(err).OrNil()

View File

@ -109,7 +109,11 @@ func (suite *ItemIntegrationSuite) TestItemReader_oneDrive() {
} }
// Read data for the file // Read data for the file
itemData, err := downloadItem(ctx, bh, custom.ToCustomDriveItem(driveItem)) itemData, err := downloadItem(
ctx,
bh,
suite.m365.User.DriveID,
custom.ToCustomDriveItem(driveItem))
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
size, err := io.Copy(io.Discard, itemData) size, err := io.Copy(io.Discard, itemData)
@ -292,6 +296,7 @@ func (m mockGetter) Get(
ctx context.Context, ctx context.Context,
url string, url string,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) { ) (*http.Response, error) {
return m.GetFunc(ctx, url) return m.GetFunc(ctx, url)
} }
@ -379,7 +384,7 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
return nil, clues.New("test error") return nil, clues.New("test error")
}, },
errorExpected: require.Error, errorExpected: require.Error,
rcExpected: require.Nil, rcExpected: require.NotNil,
}, },
{ {
name: "download url is empty", name: "download url is empty",
@ -416,7 +421,7 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
}, nil }, nil
}, },
errorExpected: require.Error, errorExpected: require.Error,
rcExpected: require.Nil, rcExpected: require.NotNil,
}, },
{ {
name: "non-2xx http response", name: "non-2xx http response",
@ -435,7 +440,7 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
}, nil }, nil
}, },
errorExpected: require.Error, errorExpected: require.Error,
rcExpected: require.Nil, rcExpected: require.NotNil,
}, },
} }
@ -448,9 +453,78 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
mg := mockGetter{ mg := mockGetter{
GetFunc: test.GetFunc, GetFunc: test.GetFunc,
} }
rc, err := downloadItem(ctx, mg, custom.ToCustomDriveItem(test.itemFunc())) rc, err := downloadItem(
ctx,
mg,
"driveID",
custom.ToCustomDriveItem(test.itemFunc()))
test.errorExpected(t, err, clues.ToCore(err)) test.errorExpected(t, err, clues.ToCore(err))
test.rcExpected(t, rc) test.rcExpected(t, rc, "reader should only be nil if item is nil")
})
}
}
func (suite *ItemUnitTestSuite) TestDownloadItem_urlByFileSize() {
var (
testRc = io.NopCloser(bytes.NewReader([]byte("test")))
url = "https://example.com"
okResp = &http.Response{
StatusCode: http.StatusOK,
Body: testRc,
}
)
table := []struct {
name string
itemFunc func() models.DriveItemable
GetFunc func(ctx context.Context, url string) (*http.Response, error)
errorExpected require.ErrorAssertionFunc
rcExpected require.ValueAssertionFunc
label string
}{
{
name: "big file",
itemFunc: func() models.DriveItemable {
di := api.NewDriveItem("test", false)
di.SetAdditionalData(map[string]any{"@microsoft.graph.downloadUrl": url})
di.SetSize(ptr.To[int64](20 * gigabyte))
return di
},
GetFunc: func(ctx context.Context, url string) (*http.Response, error) {
assert.Contains(suite.T(), url, "/content")
return okResp, nil
},
},
{
name: "small file",
itemFunc: func() models.DriveItemable {
di := api.NewDriveItem("test", false)
di.SetAdditionalData(map[string]any{"@microsoft.graph.downloadUrl": url})
di.SetSize(ptr.To[int64](2 * gigabyte))
return di
},
GetFunc: func(ctx context.Context, url string) (*http.Response, error) {
assert.NotContains(suite.T(), url, "/content")
return okResp, nil
},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
_, err := downloadItem(
ctx,
mockGetter{GetFunc: test.GetFunc},
"driveID",
custom.ToCustomDriveItem(test.itemFunc()))
require.NoError(t, err, clues.ToCore(err))
}) })
} }
} }
@ -507,7 +581,11 @@ func (suite *ItemUnitTestSuite) TestDownloadItem_ConnectionResetErrorOnFirstRead
mg := mockGetter{ mg := mockGetter{
GetFunc: GetFunc, GetFunc: GetFunc,
} }
rc, err := downloadItem(ctx, mg, custom.ToCustomDriveItem(itemFunc())) rc, err := downloadItem(
ctx,
mg,
"driveID",
custom.ToCustomDriveItem(itemFunc()))
errorExpected(t, err, clues.ToCore(err)) errorExpected(t, err, clues.ToCore(err))
rcExpected(t, rc) rcExpected(t, rc)

View File

@ -93,8 +93,9 @@ func (h siteBackupHandler) Get(
ctx context.Context, ctx context.Context,
url string, url string,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) { ) (*http.Response, error) {
return h.ac.Get(ctx, url, headers) return h.ac.Get(ctx, url, headers, requireAuth)
} }
func (h siteBackupHandler) PathPrefix( func (h siteBackupHandler) PathPrefix(

View File

@ -154,7 +154,8 @@ func (suite *URLCacheIntegrationSuite) TestURLCacheBasic() {
http.MethodGet, http.MethodGet,
props.downloadURL, props.downloadURL,
nil, nil,
nil) nil,
false)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
require.NotNil(t, resp) require.NotNil(t, resp)

View File

@ -93,8 +93,9 @@ func (h userDriveBackupHandler) Get(
ctx context.Context, ctx context.Context,
url string, url string,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) { ) (*http.Response, error) {
return h.ac.Get(ctx, url, headers) return h.ac.Get(ctx, url, headers, requireAuth)
} }
func (h userDriveBackupHandler) PathPrefix( func (h userDriveBackupHandler) PathPrefix(

View File

@ -296,6 +296,7 @@ func populateCollections(
cl), cl),
qp.ProtectedResource.ID(), qp.ProtectedResource.ID(),
bh.itemHandler(), bh.itemHandler(),
bh,
addAndRem.Added, addAndRem.Added,
addAndRem.Removed, addAndRem.Removed,
// TODO: produce a feature flag that allows selective // TODO: produce a feature flag that allows selective

View File

@ -88,6 +88,14 @@ func (bh mockBackupHandler) folderGetter() containerGetter { return
func (bh mockBackupHandler) previewIncludeContainers() []string { return bh.previewIncludes } func (bh mockBackupHandler) previewIncludeContainers() []string { return bh.previewIncludes }
func (bh mockBackupHandler) previewExcludeContainers() []string { return bh.previewExcludes } func (bh mockBackupHandler) previewExcludeContainers() []string { return bh.previewExcludes }
func (bh mockBackupHandler) CanSkipItemFailure(
err error,
resourceID string,
opts control.Options,
) (fault.SkipCause, bool) {
return "", false
}
func (bh mockBackupHandler) NewContainerCache( func (bh mockBackupHandler) NewContainerCache(
userID string, userID string,
) (string, graph.ContainerResolver) { ) (string, graph.ContainerResolver) {

View File

@ -19,6 +19,7 @@ import (
"github.com/alcionai/corso/src/internal/m365/support" "github.com/alcionai/corso/src/internal/m365/support"
"github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/internal/observe"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/count" "github.com/alcionai/corso/src/pkg/count"
"github.com/alcionai/corso/src/pkg/errs/core" "github.com/alcionai/corso/src/pkg/errs/core"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
@ -68,21 +69,21 @@ func getItemAndInfo(
ctx context.Context, ctx context.Context,
getter itemGetterSerializer, getter itemGetterSerializer,
userID string, userID string,
id string, itemID string,
useImmutableIDs bool, useImmutableIDs bool,
parentPath string, parentPath string,
) ([]byte, *details.ExchangeInfo, error) { ) ([]byte, *details.ExchangeInfo, error) {
item, info, err := getter.GetItem( item, info, err := getter.GetItem(
ctx, ctx,
userID, userID,
id, itemID,
fault.New(true)) // temporary way to force a failFast error fault.New(true)) // temporary way to force a failFast error
if err != nil { if err != nil {
return nil, nil, clues.WrapWC(ctx, err, "fetching item"). return nil, nil, clues.WrapWC(ctx, err, "fetching item").
Label(fault.LabelForceNoBackupCreation) Label(fault.LabelForceNoBackupCreation)
} }
itemData, err := getter.Serialize(ctx, item, userID, id) itemData, err := getter.Serialize(ctx, item, userID, itemID)
if err != nil { if err != nil {
return nil, nil, clues.WrapWC(ctx, err, "serializing item") return nil, nil, clues.WrapWC(ctx, err, "serializing item")
} }
@ -108,6 +109,7 @@ func NewCollection(
bc data.BaseCollection, bc data.BaseCollection,
user string, user string,
items itemGetterSerializer, items itemGetterSerializer,
canSkipFailChecker canSkipItemFailurer,
origAdded map[string]time.Time, origAdded map[string]time.Time,
origRemoved []string, origRemoved []string,
validModTimes bool, validModTimes bool,
@ -140,6 +142,7 @@ func NewCollection(
added: added, added: added,
removed: removed, removed: removed,
getter: items, getter: items,
skipChecker: canSkipFailChecker,
statusUpdater: statusUpdater, statusUpdater: statusUpdater,
} }
} }
@ -150,6 +153,7 @@ func NewCollection(
added: added, added: added,
removed: removed, removed: removed,
getter: items, getter: items,
skipChecker: canSkipFailChecker,
statusUpdater: statusUpdater, statusUpdater: statusUpdater,
counter: counter, counter: counter,
} }
@ -167,7 +171,8 @@ type prefetchCollection struct {
// removed is a list of item IDs that were deleted from, or moved out, of a container // removed is a list of item IDs that were deleted from, or moved out, of a container
removed map[string]struct{} removed map[string]struct{}
getter itemGetterSerializer getter itemGetterSerializer
skipChecker canSkipItemFailurer
statusUpdater support.StatusUpdater statusUpdater support.StatusUpdater
} }
@ -194,11 +199,12 @@ func (col *prefetchCollection) streamItems(
wg sync.WaitGroup wg sync.WaitGroup
progressMessage chan<- struct{} progressMessage chan<- struct{}
user = col.user user = col.user
dataCategory = col.Category().String()
) )
ctx = clues.Add( ctx = clues.Add(
ctx, ctx,
"category", col.Category().String()) "category", dataCategory)
defer func() { defer func() {
close(stream) close(stream)
@ -227,7 +233,7 @@ func (col *prefetchCollection) streamItems(
defer close(semaphoreCh) defer close(semaphoreCh)
// delete all removed items // delete all removed items
for id := range col.removed { for itemID := range col.removed {
semaphoreCh <- struct{}{} semaphoreCh <- struct{}{}
wg.Add(1) wg.Add(1)
@ -247,7 +253,7 @@ func (col *prefetchCollection) streamItems(
if progressMessage != nil { if progressMessage != nil {
progressMessage <- struct{}{} progressMessage <- struct{}{}
} }
}(id) }(itemID)
} }
var ( var (
@ -256,7 +262,7 @@ func (col *prefetchCollection) streamItems(
) )
// add any new items // add any new items
for id := range col.added { for itemID := range col.added {
if el.Failure() != nil { if el.Failure() != nil {
break break
} }
@ -277,8 +283,23 @@ func (col *prefetchCollection) streamItems(
col.Opts().ToggleFeatures.ExchangeImmutableIDs, col.Opts().ToggleFeatures.ExchangeImmutableIDs,
parentPath) parentPath)
if err != nil { if err != nil {
// pulled outside the switch due to multiple return values.
cause, canSkip := col.skipChecker.CanSkipItemFailure(
err,
user,
col.Opts())
// Handle known error cases // Handle known error cases
switch { switch {
case canSkip:
// this is a special case handler that allows the item to be skipped
// instead of producing an error.
errs.AddSkip(ctx, fault.FileSkip(
cause,
dataCategory,
id,
id,
nil))
case errors.Is(err, core.ErrNotFound): case errors.Is(err, core.ErrNotFound):
// Don't report errors for deleted items as there's no way for us to // Don't report errors for deleted items as there's no way for us to
// back up data that is gone. Record it as a "success", since there's // back up data that is gone. Record it as a "success", since there's
@ -349,7 +370,7 @@ func (col *prefetchCollection) streamItems(
if progressMessage != nil { if progressMessage != nil {
progressMessage <- struct{}{} progressMessage <- struct{}{}
} }
}(id) }(itemID)
} }
wg.Wait() wg.Wait()
@ -377,7 +398,8 @@ type lazyFetchCollection struct {
// removed is a list of item IDs that were deleted from, or moved out, of a container // removed is a list of item IDs that were deleted from, or moved out, of a container
removed map[string]struct{} removed map[string]struct{}
getter itemGetterSerializer getter itemGetterSerializer
skipChecker canSkipItemFailurer
statusUpdater support.StatusUpdater statusUpdater support.StatusUpdater
@ -404,8 +426,8 @@ func (col *lazyFetchCollection) streamItems(
var ( var (
success int64 success int64
progressMessage chan<- struct{} progressMessage chan<- struct{}
user = col.user
user = col.user el = errs.Local()
) )
defer func() { defer func() {
@ -417,7 +439,7 @@ func (col *lazyFetchCollection) streamItems(
int(success), int(success),
0, 0,
col.FullPath().Folder(false), col.FullPath().Folder(false),
errs.Failure()) el.Failure())
}() }()
if len(col.added)+len(col.removed) > 0 { if len(col.added)+len(col.removed) > 0 {
@ -443,7 +465,7 @@ func (col *lazyFetchCollection) streamItems(
// add any new items // add any new items
for id, modTime := range col.added { for id, modTime := range col.added {
if errs.Failure() != nil { if el.Failure() != nil {
break break
} }
@ -459,15 +481,18 @@ func (col *lazyFetchCollection) streamItems(
&lazyItemGetter{ &lazyItemGetter{
userID: user, userID: user,
itemID: id, itemID: id,
category: col.Category(),
getter: col.getter, getter: col.getter,
modTime: modTime, modTime: modTime,
immutableIDs: col.Opts().ToggleFeatures.ExchangeImmutableIDs, immutableIDs: col.Opts().ToggleFeatures.ExchangeImmutableIDs,
parentPath: parentPath, parentPath: parentPath,
skipChecker: col.skipChecker,
opts: col.Opts(),
}, },
id, id,
modTime, modTime,
col.counter, col.counter,
errs) el)
atomic.AddInt64(&success, 1) atomic.AddInt64(&success, 1)
@ -481,9 +506,12 @@ type lazyItemGetter struct {
getter itemGetterSerializer getter itemGetterSerializer
userID string userID string
itemID string itemID string
category path.CategoryType
parentPath string parentPath string
modTime time.Time modTime time.Time
immutableIDs bool immutableIDs bool
skipChecker canSkipItemFailurer
opts control.Options
} }
func (lig *lazyItemGetter) GetData( func (lig *lazyItemGetter) GetData(
@ -498,6 +526,25 @@ func (lig *lazyItemGetter) GetData(
lig.immutableIDs, lig.immutableIDs,
lig.parentPath) lig.parentPath)
if err != nil { if err != nil {
if lig.skipChecker != nil {
cause, canSkip := lig.skipChecker.CanSkipItemFailure(
err,
lig.userID,
lig.opts)
if canSkip {
errs.AddSkip(ctx, fault.FileSkip(
cause,
lig.category.String(),
lig.itemID,
lig.itemID,
nil))
return nil, nil, false, clues.
NewWC(ctx, "error marked as skippable by handler").
Label(graph.LabelsSkippable)
}
}
// If an item was deleted then return an empty file so we don't fail // If an item was deleted then return an empty file so we don't fail
// the backup and return a sentinel error when asked for ItemInfo so // the backup and return a sentinel error when asked for ItemInfo so
// we don't display the item in the backup. // we don't display the item in the backup.
@ -512,7 +559,7 @@ func (lig *lazyItemGetter) GetData(
err = clues.Stack(err) err = clues.Stack(err)
errs.AddRecoverable(ctx, err) errs.AddRecoverable(ctx, err)
return nil, nil, false, err return nil, nil, false, clues.Stack(err)
} }
// Update the mod time to what we already told kopia about. This is required // Update the mod time to what we already told kopia about. This is required

View File

@ -28,6 +28,7 @@ import (
"github.com/alcionai/corso/src/pkg/errs/core" "github.com/alcionai/corso/src/pkg/errs/core"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata" graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
@ -153,6 +154,7 @@ func (suite *CollectionUnitSuite) TestNewCollection_state() {
count.New()), count.New()),
"u", "u",
mock.DefaultItemGetSerialize(), mock.DefaultItemGetSerialize(),
mock.NeverCanSkipFailChecker(),
nil, nil,
nil, nil,
colType.validModTimes, colType.validModTimes,
@ -298,6 +300,7 @@ func (suite *CollectionUnitSuite) TestPrefetchCollection_Items() {
count.New()), count.New()),
"", "",
&mock.ItemGetSerialize{}, &mock.ItemGetSerialize{},
mock.NeverCanSkipFailChecker(),
test.added, test.added,
maps.Keys(test.removed), maps.Keys(test.removed),
false, false,
@ -333,6 +336,232 @@ func (suite *CollectionUnitSuite) TestPrefetchCollection_Items() {
} }
} }
func (suite *CollectionUnitSuite) TestPrefetchCollection_Items_skipFailure() {
var (
start = time.Now().Add(-time.Second)
statusUpdater = func(*support.ControllerOperationStatus) {}
)
table := []struct {
name string
category path.CategoryType
handler backupHandler
added map[string]time.Time
removed map[string]struct{}
expectItemCount int
expectSkippedCount int
expectErr assert.ErrorAssertionFunc
}{
{
name: "no items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
expectErr: assert.NoError,
},
{
name: "events only added items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
added: map[string]time.Time{
"fisher": {},
"flannigan": {},
"fitzbog": {},
},
expectItemCount: 0,
expectSkippedCount: 3,
expectErr: assert.NoError,
},
{
name: "events only removed items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
removed: map[string]struct{}{
"princess": {},
"poppy": {},
"petunia": {},
},
expectItemCount: 3,
expectSkippedCount: 0,
expectErr: assert.NoError,
},
{
name: "events added and removed items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
added: map[string]time.Time{
"general": {},
},
removed: map[string]struct{}{
"general": {},
"goose": {},
"grumbles": {},
},
expectItemCount: 3,
// not 1, because general is removed from the added
// map due to being in the removed map
expectSkippedCount: 0,
expectErr: assert.NoError,
},
{
name: "contacts only added items",
category: path.ContactsCategory,
handler: newContactBackupHandler(api.Client{}),
added: map[string]time.Time{
"fisher": {},
"flannigan": {},
"fitzbog": {},
},
expectItemCount: 0,
expectSkippedCount: 0,
expectErr: assert.Error,
},
{
name: "contacts only removed items",
category: path.ContactsCategory,
handler: newContactBackupHandler(api.Client{}),
removed: map[string]struct{}{
"princess": {},
"poppy": {},
"petunia": {},
},
expectItemCount: 3,
expectSkippedCount: 0,
expectErr: assert.NoError,
},
{
name: "contacts added and removed items",
category: path.ContactsCategory,
handler: newContactBackupHandler(api.Client{}),
added: map[string]time.Time{
"general": {},
},
removed: map[string]struct{}{
"general": {},
"goose": {},
"grumbles": {},
},
expectItemCount: 3,
// not 1, because general is removed from the added
// map due to being in the removed map
expectSkippedCount: 0,
expectErr: assert.NoError,
},
{
name: "mail only added items",
category: path.EmailCategory,
handler: newMailBackupHandler(api.Client{}),
added: map[string]time.Time{
"fisher": {},
"flannigan": {},
"fitzbog": {},
},
expectItemCount: 0,
expectSkippedCount: 0,
expectErr: assert.Error,
},
{
name: "mail only removed items",
category: path.EmailCategory,
handler: newMailBackupHandler(api.Client{}),
removed: map[string]struct{}{
"princess": {},
"poppy": {},
"petunia": {},
},
expectItemCount: 3,
expectSkippedCount: 0,
expectErr: assert.NoError,
},
{
name: "mail added and removed items",
category: path.EmailCategory,
handler: newMailBackupHandler(api.Client{}),
added: map[string]time.Time{
"general": {},
},
removed: map[string]struct{}{
"general": {},
"goose": {},
"grumbles": {},
},
expectItemCount: 3,
// not 1, because general is removed from the added
// map due to being in the removed map
expectSkippedCount: 0,
expectErr: assert.NoError,
},
}
for _, test := range table {
suite.Run(test.name, func() {
var (
t = suite.T()
errs = fault.New(true)
itemCount int
)
ctx, flush := tester.NewContext(t)
defer flush()
fullPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
require.NoError(t, err, clues.ToCore(err))
locPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
require.NoError(t, err, clues.ToCore(err))
opts := control.DefaultOptions()
opts.SkipEventsOnInstance503ForResources = map[string]struct{}{}
opts.SkipEventsOnInstance503ForResources["pr"] = struct{}{}
col := NewCollection(
data.NewBaseCollection(
fullPath,
nil,
locPath.ToBuilder(),
opts,
false,
count.New()),
"pr",
&mock.ItemGetSerialize{
SerializeErr: graph.ErrServiceUnavailableEmptyResp,
},
test.handler,
test.added,
maps.Keys(test.removed),
false,
statusUpdater,
count.New())
for item := range col.Items(ctx, errs) {
itemCount++
_, rok := test.removed[item.ID()]
if rok {
dimt, ok := item.(data.ItemModTime)
require.True(t, ok, "item implements data.ItemModTime")
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
assert.True(t, item.Deleted(), "removals should be marked as deleted")
}
_, aok := test.added[item.ID()]
if !rok && aok {
assert.False(t, item.Deleted(), "additions should not be marked as deleted")
}
assert.True(t, aok || rok, "item must be either added or removed: %q", item.ID())
}
test.expectErr(t, errs.Failure())
assert.Equal(
t,
test.expectItemCount,
itemCount,
"should see all expected items")
assert.Len(t, errs.Skipped(), test.expectSkippedCount)
})
}
}
// This test verifies skipped error cases are handled correctly by collection enumeration // This test verifies skipped error cases are handled correctly by collection enumeration
func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() { func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() {
var ( var (
@ -398,6 +627,7 @@ func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() {
count.New()), count.New()),
"", "",
test.itemGetter, test.itemGetter,
mock.NeverCanSkipFailChecker(),
test.added, test.added,
nil, nil,
false, false,
@ -478,6 +708,7 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
expectItemCount: 3, expectItemCount: 3,
expectReads: []string{ expectReads: []string{
"fisher", "fisher",
"flannigan",
"fitzbog", "fitzbog",
}, },
}, },
@ -530,6 +761,7 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
count.New()), count.New()),
"", "",
mlg, mlg,
mock.NeverCanSkipFailChecker(),
test.added, test.added,
maps.Keys(test.removed), maps.Keys(test.removed),
true, true,
@ -541,10 +773,10 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
_, rok := test.removed[item.ID()] _, rok := test.removed[item.ID()]
if rok { if rok {
assert.True(t, item.Deleted(), "removals should be marked as deleted")
dimt, ok := item.(data.ItemModTime) dimt, ok := item.(data.ItemModTime)
require.True(t, ok, "item implements data.ItemModTime") require.True(t, ok, "item implements data.ItemModTime")
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()") assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
assert.True(t, item.Deleted(), "removals should be marked as deleted")
} }
modTime, aok := test.added[item.ID()] modTime, aok := test.added[item.ID()]
@ -553,7 +785,6 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
// initializer. // initializer.
assert.Implements(t, (*data.ItemModTime)(nil), item) assert.Implements(t, (*data.ItemModTime)(nil), item)
assert.Equal(t, modTime, item.(data.ItemModTime).ModTime(), "item mod time") assert.Equal(t, modTime, item.(data.ItemModTime).ModTime(), "item mod time")
assert.False(t, item.Deleted(), "additions should not be marked as deleted") assert.False(t, item.Deleted(), "additions should not be marked as deleted")
// Check if the test want's us to read the item's data so the lazy // Check if the test want's us to read the item's data so the lazy
@ -573,6 +804,8 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
// collection initializer. // collection initializer.
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
assert.Equal(t, modTime, info.Modified(), "ItemInfo mod time") assert.Equal(t, modTime, info.Modified(), "ItemInfo mod time")
} else {
assert.Fail(t, "unexpected read on item %s", item.ID())
} }
} }
@ -589,6 +822,294 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
} }
} }
func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_skipFailure() {
var (
start = time.Now().Add(-time.Second)
statusUpdater = func(*support.ControllerOperationStatus) {}
expectSkip = func(t *testing.T, err error) {
assert.Error(t, err, clues.ToCore(err))
assert.ErrorContains(t, err, "skip")
assert.True(t, clues.HasLabel(err, graph.LabelsSkippable), clues.ToCore(err))
}
expectNotSkipped = func(t *testing.T, err error) {
assert.Error(t, err, clues.ToCore(err))
assert.NotContains(t, err.Error(), "skip")
}
)
table := []struct {
name string
added map[string]time.Time
removed map[string]struct{}
category path.CategoryType
handler backupHandler
expectItemCount int
expectSkippedCount int
expectReads []string
expectErr func(t *testing.T, err error)
expectFailure assert.ErrorAssertionFunc
}{
{
name: "no items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
expectFailure: assert.NoError,
},
{
name: "events only added items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
added: map[string]time.Time{
"fisher": start.Add(time.Minute),
"flannigan": start.Add(2 * time.Minute),
"fitzbog": start.Add(3 * time.Minute),
},
expectItemCount: 3,
expectSkippedCount: 3,
expectReads: []string{
"fisher",
"flannigan",
"fitzbog",
},
expectErr: expectSkip,
expectFailure: assert.NoError,
},
{
name: "events only removed items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
removed: map[string]struct{}{
"princess": {},
"poppy": {},
"petunia": {},
},
expectItemCount: 3,
expectSkippedCount: 0,
expectErr: expectSkip,
expectFailure: assert.NoError,
},
{
name: "events added and removed items",
category: path.EventsCategory,
handler: newEventBackupHandler(api.Client{}),
added: map[string]time.Time{
"general": {},
},
removed: map[string]struct{}{
"general": {},
"goose": {},
"grumbles": {},
},
expectItemCount: 3,
// not 1, because general is removed from the added
// map due to being in the removed map
expectSkippedCount: 0,
expectErr: expectSkip,
expectFailure: assert.NoError,
},
{
name: "contacts only added items",
category: path.ContactsCategory,
handler: newContactBackupHandler(api.Client{}),
added: map[string]time.Time{
"fisher": start.Add(time.Minute),
"flannigan": start.Add(2 * time.Minute),
"fitzbog": start.Add(3 * time.Minute),
},
expectItemCount: 3,
expectSkippedCount: 0,
expectReads: []string{
"fisher",
"flannigan",
"fitzbog",
},
expectErr: expectNotSkipped,
expectFailure: assert.Error,
},
{
name: "contacts only removed items",
category: path.ContactsCategory,
handler: newContactBackupHandler(api.Client{}),
removed: map[string]struct{}{
"princess": {},
"poppy": {},
"petunia": {},
},
expectItemCount: 3,
expectSkippedCount: 0,
expectErr: expectNotSkipped,
expectFailure: assert.NoError,
},
{
name: "contacts added and removed items",
category: path.ContactsCategory,
handler: newContactBackupHandler(api.Client{}),
added: map[string]time.Time{
"general": {},
},
removed: map[string]struct{}{
"general": {},
"goose": {},
"grumbles": {},
},
expectItemCount: 3,
// not 1, because general is removed from the added
// map due to being in the removed map
expectSkippedCount: 0,
expectErr: expectNotSkipped,
expectFailure: assert.NoError,
},
{
name: "mail only added items",
category: path.EmailCategory,
handler: newMailBackupHandler(api.Client{}),
added: map[string]time.Time{
"fisher": start.Add(time.Minute),
"flannigan": start.Add(2 * time.Minute),
"fitzbog": start.Add(3 * time.Minute),
},
expectItemCount: 3,
expectSkippedCount: 0,
expectReads: []string{
"fisher",
"flannigan",
"fitzbog",
},
expectErr: expectNotSkipped,
expectFailure: assert.Error,
},
{
name: "mail only removed items",
category: path.EmailCategory,
handler: newMailBackupHandler(api.Client{}),
removed: map[string]struct{}{
"princess": {},
"poppy": {},
"petunia": {},
},
expectItemCount: 3,
expectSkippedCount: 0,
expectErr: expectNotSkipped,
expectFailure: assert.NoError,
},
{
name: "mail added and removed items",
category: path.EmailCategory,
handler: newMailBackupHandler(api.Client{}),
added: map[string]time.Time{
"general": {},
},
removed: map[string]struct{}{
"general": {},
"goose": {},
"grumbles": {},
},
expectItemCount: 3,
// not 1, because general is removed from the added
// map due to being in the removed map
expectSkippedCount: 0,
expectErr: expectNotSkipped,
expectFailure: assert.NoError,
},
}
for _, test := range table {
suite.Run(test.name, func() {
var (
t = suite.T()
errs = fault.New(false)
itemCount int
)
ctx, flush := tester.NewContext(t)
defer flush()
fullPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
require.NoError(t, err, clues.ToCore(err))
locPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
require.NoError(t, err, clues.ToCore(err))
mlg := &mockLazyItemGetterSerializer{
ItemGetSerialize: &mock.ItemGetSerialize{
SerializeErr: graph.ErrServiceUnavailableEmptyResp,
},
}
defer mlg.check(t, test.expectReads)
opts := control.DefaultOptions()
opts.SkipEventsOnInstance503ForResources = map[string]struct{}{}
opts.SkipEventsOnInstance503ForResources["pr"] = struct{}{}
col := NewCollection(
data.NewBaseCollection(
fullPath,
nil,
locPath.ToBuilder(),
opts,
false,
count.New()),
"pr",
mlg,
test.handler,
test.added,
maps.Keys(test.removed),
true,
statusUpdater,
count.New())
for item := range col.Items(ctx, errs) {
itemCount++
_, rok := test.removed[item.ID()]
if rok {
dimt, ok := item.(data.ItemModTime)
require.True(t, ok, "item implements data.ItemModTime")
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
assert.True(t, item.Deleted(), "removals should be marked as deleted")
}
modTime, aok := test.added[item.ID()]
if !rok && aok {
// Item's mod time should be what's passed into the collection
// initializer.
assert.Implements(t, (*data.ItemModTime)(nil), item)
assert.Equal(t, modTime, item.(data.ItemModTime).ModTime(), "item mod time")
assert.False(t, item.Deleted(), "additions should not be marked as deleted")
// Check if the test want's us to read the item's data so the lazy
// data fetch is executed.
if slices.Contains(test.expectReads, item.ID()) {
r := item.ToReader()
_, err := io.ReadAll(r)
test.expectErr(t, err)
r.Close()
} else {
assert.Fail(t, "unexpected read on item %s", item.ID())
}
}
assert.True(t, aok || rok, "item must be either added or removed: %q", item.ID())
}
failure := errs.Failure()
if failure == nil && len(errs.Recovered()) > 0 {
failure = errs.Recovered()[0]
}
test.expectFailure(t, failure, clues.ToCore(failure))
assert.Equal(
t,
test.expectItemCount,
itemCount,
"should see all expected items")
assert.Len(t, errs.Skipped(), test.expectSkippedCount)
})
}
}
func (suite *CollectionUnitSuite) TestLazyItem_NoRead_GetInfo_Errors() { func (suite *CollectionUnitSuite) TestLazyItem_NoRead_GetInfo_Errors() {
t := suite.T() t := suite.T()

View File

@ -1,6 +1,8 @@
package exchange package exchange
import ( import (
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
) )
@ -52,3 +54,11 @@ func (h contactBackupHandler) NewContainerCache(
getter: h.ac, getter: h.ac,
} }
} }
func (h contactBackupHandler) CanSkipItemFailure(
err error,
resourceID string,
opts control.Options,
) (fault.SkipCause, bool) {
return "", false
}

View File

@ -0,0 +1,83 @@
package exchange
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
type ContactsBackupHandlerUnitSuite struct {
tester.Suite
}
func TestContactsBackupHandlerUnitSuite(t *testing.T) {
suite.Run(t, &ContactsBackupHandlerUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *ContactsBackupHandlerUnitSuite) TestHandler_CanSkipItemFailure() {
resourceID := uuid.NewString()
table := []struct {
name string
err error
opts control.Options
expect assert.BoolAssertionFunc
expectCause fault.SkipCause
}{
{
name: "no config",
err: assert.AnError,
opts: control.Options{},
expect: assert.False,
},
{
name: "false when map is empty",
err: assert.AnError,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{},
},
expect: assert.False,
},
{
name: "false on nil error",
err: nil,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.False,
},
{
name: "false even if resource matches",
err: assert.AnError,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.False,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
h := newContactBackupHandler(api.Client{})
cause, result := h.CanSkipItemFailure(
test.err,
resourceID,
test.opts)
test.expect(t, result)
assert.Equal(t, test.expectCause, cause)
})
}
}

View File

@ -3,11 +3,13 @@ package exchange
import ( import (
"context" "context"
"fmt" "fmt"
"hash/crc32"
stdpath "path" stdpath "path"
"testing" "testing"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -1017,6 +1019,210 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestAddToCache() {
assert.Equal(t, m.expectedLocation, l.String(), "location path") assert.Equal(t, m.expectedLocation, l.String(), "location path")
} }
// ---------------------------------------------------------------------------
// EventContainerCache unit tests
// ---------------------------------------------------------------------------
var _ containerGetter = mockEventContainerGetter{}
type mockEventContainerGetter struct {
// containerGetter returns graph.CalendarDisplayable, unlike containersEnumerator
// which returns models.Calendarable.
idToCalendar map[string]graph.CalendarDisplayable
err error
}
func (m mockEventContainerGetter) GetContainerByID(
ctx context.Context,
userID string,
dirID string,
) (graph.Container, error) {
return m.idToCalendar[dirID], m.err
}
var _ containersEnumerator[models.Calendarable] = mockEventContainersEnumerator{}
type mockEventContainersEnumerator struct {
containers []models.Calendarable
err error
}
func (m mockEventContainersEnumerator) EnumerateContainers(
ctx context.Context,
userID string,
baseDirID string,
) ([]models.Calendarable, error) {
return m.containers, m.err
}
type EventsContainerUnitSuite struct {
tester.Suite
}
func TestEventsContainerUnitSuite(t *testing.T) {
suite.Run(t, &EventsContainerUnitSuite{
Suite: tester.NewUnitSuite(t),
})
}
func makeCalendar(
id, name, ownerEmail string,
isDefault bool,
) *models.Calendar {
c := models.NewCalendar()
c.SetId(ptr.To(id))
c.SetName(ptr.To(name))
c.SetIsDefaultCalendar(ptr.To(isDefault))
if len(ownerEmail) > 0 {
email := models.NewEmailAddress()
email.SetAddress(ptr.To(ownerEmail))
// Set crc as the name for keeping this func simple.
eName := fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(ownerEmail)))
email.SetName(ptr.To(eName))
c.SetOwner(email)
}
return c
}
// Test if we skip backup of shared calendars. These will be backed up for
// the resource owner that owns the calendar.
func (suite *EventsContainerUnitSuite) TestPopulate_SkipSharedCalendars() {
// map of calendars
calendars := map[string]models.Calendarable{
// Default calendars Dx
"D0": makeCalendar(api.DefaultCalendar, api.DefaultCalendar, "owner@bar.com", true),
// Atypical, but creating another default calendar for testing purposes.
"D1": makeCalendar("D1", "D1", "owner@bar.com", true),
// Shared calendars Sx
"S0": makeCalendar("S0", "S0", "sharer@bar.com", false),
// Owned calendars, not default Ox
"O0": makeCalendar("O0", "O0", "owner@bar.com", false),
// Calendars with missing owner informaton
"M0": makeCalendar("M0", "M0", "", false),
}
// Always return default calendar from the getter.
getContainersByID := func() map[string]graph.CalendarDisplayable {
return map[string]graph.CalendarDisplayable{
api.DefaultCalendar: *graph.CreateCalendarDisplayable(calendars["D0"], "parentID"),
}
}
table := []struct {
name string
enumerateContainers func() []models.Calendarable
expectErr assert.ErrorAssertionFunc
assertFunc func(t *testing.T, ecc *eventContainerCache)
}{
{
name: "one default calendar, one shared",
enumerateContainers: func() []models.Calendarable {
return []models.Calendarable{
calendars["D0"],
calendars["S0"],
}
},
expectErr: assert.NoError,
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
assert.Len(t, ecc.cache, 1, "expected calendar count")
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
},
},
{
name: "2 default calendars, 1 shared",
enumerateContainers: func() []models.Calendarable {
return []models.Calendarable{
calendars["D0"],
calendars["D1"],
calendars["S0"],
}
},
expectErr: assert.NoError,
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
assert.Len(t, ecc.cache, 2, "expected calendar count")
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
assert.NotNil(t, ecc.cache["D1"], "missing default calendar")
},
},
{
name: "1 default, 1 additional owned, 1 shared",
enumerateContainers: func() []models.Calendarable {
return []models.Calendarable{
calendars["D0"],
calendars["O0"],
calendars["S0"],
}
},
expectErr: assert.NoError,
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
assert.Len(t, ecc.cache, 2, "expected calendar count")
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
assert.NotNil(t, ecc.cache["O0"], "missing owned calendar")
},
},
{
name: "1 default, 1 with missing owner information",
enumerateContainers: func() []models.Calendarable {
return []models.Calendarable{
calendars["D0"],
calendars["M0"],
}
},
expectErr: assert.NoError,
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
assert.Len(t, ecc.cache, 2, "expected calendar count")
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
assert.NotNil(t, ecc.cache["M0"], "missing calendar with missing owner info")
},
},
{
// Unlikely to happen, but we should back up the calendar if the default owner
// cannot be determined, i.e. default calendar is missing.
name: "default owner info missing",
enumerateContainers: func() []models.Calendarable {
return []models.Calendarable{
calendars["S0"],
}
},
expectErr: assert.NoError,
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
assert.Len(t, ecc.cache, 2, "expected calendar count")
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
assert.NotNil(t, ecc.cache["S0"], "missing additional calendar")
},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
ecc := &eventContainerCache{
userID: "test",
enumer: mockEventContainersEnumerator{containers: test.enumerateContainers()},
getter: mockEventContainerGetter{idToCalendar: getContainersByID()},
}
err := ecc.Populate(ctx, fault.New(true), "root", "root")
test.expectErr(t, err, clues.ToCore(err))
test.assertFunc(t, ecc)
})
}
}
// ---------------------------------------------------------------------------
// container resolver integration suite
// ---------------------------------------------------------------------------
type ContainerResolverIntgSuite struct { type ContainerResolverIntgSuite struct {
tester.Suite tester.Suite
m365 its.M365IntgTestSetup m365 its.M365IntgTestSetup

View File

@ -1,6 +1,13 @@
package exchange package exchange
import ( import (
"errors"
"net/http"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
) )
@ -52,3 +59,32 @@ func (h eventBackupHandler) NewContainerCache(
getter: h.ac, getter: h.ac,
} }
} }
// todo: this could be further improved buy specifying the call source and matching that
// with the expected error. Might be necessary if we use this for more than one error.
// But since we only call this in a single place at this time, that additional guard isn't
// built into the func.
func (h eventBackupHandler) CanSkipItemFailure(
err error,
resourceID string,
opts control.Options,
) (fault.SkipCause, bool) {
if err == nil {
return "", false
}
// this is a bit overly cautious. we do know that we get 503s with empty response bodies
// due to fauilures when getting too many instances. We don't know for sure if we get
// generic, well formed 503s. But since we're working with specific resources and item
// IDs in the first place, that extra caution will help make sure an unexpected error dosn't
// slip through the cracks on us.
if !errors.Is(err, graph.ErrServiceUnavailableEmptyResp) &&
!clues.HasLabel(err, graph.LabelStatus(http.StatusServiceUnavailable)) {
return "", false
}
_, ok := opts.SkipEventsOnInstance503ForResources[resourceID]
// strict equals required here. ids are case sensitive.
return fault.SkipKnownEventInstance503s, ok
}

View File

@ -0,0 +1,112 @@
package exchange
import (
"net/http"
"testing"
"github.com/alcionai/clues"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
)
type EventsBackupHandlerUnitSuite struct {
tester.Suite
}
func TestEventsBackupHandlerUnitSuite(t *testing.T) {
suite.Run(t, &EventsBackupHandlerUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *EventsBackupHandlerUnitSuite) TestHandler_CanSkipItemFailure() {
resourceID := uuid.NewString()
table := []struct {
name string
err error
opts control.Options
expect assert.BoolAssertionFunc
expectCause fault.SkipCause
}{
{
name: "no config",
err: graph.ErrServiceUnavailableEmptyResp,
opts: control.Options{},
expect: assert.False,
expectCause: fault.SkipKnownEventInstance503s,
},
{
name: "empty skip on 503",
err: graph.ErrServiceUnavailableEmptyResp,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{},
},
expect: assert.False,
expectCause: fault.SkipKnownEventInstance503s,
},
{
name: "nil error",
err: nil,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.False,
},
{
name: "non-matching resource",
err: graph.ErrServiceUnavailableEmptyResp,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
"foo": {},
},
},
expect: assert.False,
expectCause: fault.SkipKnownEventInstance503s,
},
{
name: "match on instance 503 empty resp",
err: graph.ErrServiceUnavailableEmptyResp,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.True,
expectCause: fault.SkipKnownEventInstance503s,
},
{
name: "match on instance 503",
err: clues.New("arbitrary error").
Label(graph.LabelStatus(http.StatusServiceUnavailable)),
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.True,
expectCause: fault.SkipKnownEventInstance503s,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
h := newEventBackupHandler(api.Client{})
cause, result := h.CanSkipItemFailure(
test.err,
resourceID,
test.opts)
test.expect(t, result)
assert.Equal(t, test.expectCause, cause)
})
}
}

View File

@ -2,6 +2,7 @@ package exchange
import ( import (
"context" "context"
"strings"
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
@ -60,6 +61,16 @@ func (ecc *eventContainerCache) populateEventRoot(ctx context.Context) error {
return nil return nil
} }
func isSharedCalendar(defaultCalendarOwner string, c models.Calendarable) bool {
// If we can't determine the owner, assume the calendar is owned by the
// user.
if len(defaultCalendarOwner) == 0 || c.GetOwner() == nil {
return false
}
return !strings.EqualFold(defaultCalendarOwner, ptr.Val(c.GetOwner().GetAddress()))
}
// Populate utility function for populating eventCalendarCache. // Populate utility function for populating eventCalendarCache.
// Executes 1 additional Graph Query // Executes 1 additional Graph Query
// @param baseID: ignored. Present to conform to interface // @param baseID: ignored. Present to conform to interface
@ -89,11 +100,39 @@ func (ecc *eventContainerCache) Populate(
return clues.WrapWC(ctx, err, "enumerating containers") return clues.WrapWC(ctx, err, "enumerating containers")
} }
var defaultCalendarOwner string
// Determine the owner for the default calendar. We'll use this to detect and
// skip shared calendars that are not owned by this user.
for _, c := range containers {
if ptr.Val(c.GetIsDefaultCalendar()) && c.GetOwner() != nil {
defaultCalendarOwner = ptr.Val(c.GetOwner().GetAddress())
ctx = clues.Add(ctx, "default_calendar_owner", defaultCalendarOwner)
break
}
}
for _, c := range containers { for _, c := range containers {
if el.Failure() != nil { if el.Failure() != nil {
return el.Failure() return el.Failure()
} }
// Skip shared calendars if we have enough information to determine the owner
if isSharedCalendar(defaultCalendarOwner, c) {
var ownerEmail string
if c.GetOwner() != nil {
ownerEmail = ptr.Val(c.GetOwner().GetAddress())
}
logger.Ctx(ctx).Infow(
"skipping shared calendar",
"name", ptr.Val(c.GetName()),
"owner", ownerEmail)
continue
}
cacheFolder := graph.NewCacheFolder( cacheFolder := graph.NewCacheFolder(
api.CalendarDisplayable{Calendarable: c}, api.CalendarDisplayable{Calendarable: c},
path.Builder{}.Append(ptr.Val(c.GetId())), path.Builder{}.Append(ptr.Val(c.GetId())),

View File

@ -26,6 +26,8 @@ type backupHandler interface {
previewIncludeContainers() []string previewIncludeContainers() []string
previewExcludeContainers() []string previewExcludeContainers() []string
NewContainerCache(userID string) (string, graph.ContainerResolver) NewContainerCache(userID string) (string, graph.ContainerResolver)
canSkipItemFailurer
} }
type addedAndRemovedItemGetter interface { type addedAndRemovedItemGetter interface {
@ -57,6 +59,14 @@ func BackupHandlers(ac api.Client) map[path.CategoryType]backupHandler {
} }
} }
type canSkipItemFailurer interface {
CanSkipItemFailure(
err error,
resourceID string,
opts control.Options,
) (fault.SkipCause, bool)
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// restore // restore
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -1,6 +1,8 @@
package exchange package exchange
import ( import (
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "github.com/alcionai/corso/src/pkg/services/m365/api/graph"
) )
@ -57,3 +59,11 @@ func (h mailBackupHandler) NewContainerCache(
getter: h.ac, getter: h.ac,
} }
} }
func (h mailBackupHandler) CanSkipItemFailure(
err error,
resourceID string,
opts control.Options,
) (fault.SkipCause, bool) {
return "", false
}

View File

@ -0,0 +1,83 @@
package exchange
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
type MailBackupHandlerUnitSuite struct {
tester.Suite
}
func TestMailBackupHandlerUnitSuite(t *testing.T) {
suite.Run(t, &MailBackupHandlerUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *MailBackupHandlerUnitSuite) TestHandler_CanSkipItemFailure() {
resourceID := uuid.NewString()
table := []struct {
name string
err error
opts control.Options
expect assert.BoolAssertionFunc
expectCause fault.SkipCause
}{
{
name: "no config",
err: assert.AnError,
opts: control.Options{},
expect: assert.False,
},
{
name: "false when map is empty",
err: assert.AnError,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{},
},
expect: assert.False,
},
{
name: "false on nil error",
err: nil,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.False,
},
{
name: "false even if resource matches",
err: assert.AnError,
opts: control.Options{
SkipEventsOnInstance503ForResources: map[string]struct{}{
resourceID: {},
},
},
expect: assert.False,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
h := newMailBackupHandler(api.Client{})
cause, result := h.CanSkipItemFailure(
test.err,
resourceID,
test.opts)
test.expect(t, result)
assert.Equal(t, test.expectCause, cause)
})
}
}

View File

@ -3,6 +3,7 @@ package exchange
import ( import (
"context" "context"
"errors" "errors"
"regexp"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
@ -147,6 +148,8 @@ func restoreMail(
msg = setMessageSVEPs(toMessage(msg)) msg = setMessageSVEPs(toMessage(msg))
setReplyTos(msg)
attachments := msg.GetAttachments() attachments := msg.GetAttachments()
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized // Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
msg.SetAttachments([]models.Attachmentable{}) msg.SetAttachments([]models.Attachmentable{})
@ -229,6 +232,38 @@ func setMessageSVEPs(msg models.Messageable) models.Messageable {
return msg return msg
} }
func setReplyTos(msg models.Messageable) {
var (
replyTos = msg.GetReplyTo()
emailAddress models.EmailAddressable
name, address string
sanitizedReplyTos = make([]models.Recipientable, 0)
)
if len(replyTos) == 0 {
return
}
for _, replyTo := range replyTos {
emailAddress = replyTo.GetEmailAddress()
address = ptr.Val(emailAddress.GetAddress())
name = ptr.Val(emailAddress.GetName())
if isValidEmail(address) || isValidDN(address) {
newEmailAddress := models.NewEmailAddress()
newEmailAddress.SetAddress(ptr.To(address))
newEmailAddress.SetName(ptr.To(name))
sanitizedReplyTo := models.NewRecipient()
sanitizedReplyTo.SetEmailAddress(newEmailAddress)
sanitizedReplyTos = append(sanitizedReplyTos, sanitizedReplyTo)
}
}
msg.SetReplyTo(sanitizedReplyTos)
}
func (h mailRestoreHandler) GetItemsInContainerByCollisionKey( func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
@ -240,3 +275,24 @@ func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
return m, nil return m, nil
} }
// [TODO]relocate to a common place
func isValidEmail(email string) bool {
emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
r := regexp.MustCompile(emailRegex)
return r.MatchString(email)
}
// isValidDN check if given string's format matches that of a MSFT Distinguished Name
// This regular expression matches strings that start with /o=,
// followed by any characters except /,
// then /ou=, followed by any characters except /,
// then /cn=, followed by any characters except /,
// then /cn= followed by a 32-character hexadecimal string followed by - and any additional characters.
func isValidDN(dn string) bool {
dnRegex := `^/o=[^/]+/ou=[^/]+/cn=[^/]+/cn=[a-fA-F0-9]{32}-[a-zA-Z0-9-]+$`
r := regexp.MustCompile(dnRegex)
return r.MatchString(dn)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock" "github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/tester/its" "github.com/alcionai/corso/src/internal/tester/its"
@ -24,6 +25,127 @@ import (
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
//nolint:lll
const TestDN = "/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=4eca0d46a2324036b0b326dc58cfc802-user"
type RestoreMailUnitSuite struct {
tester.Suite
}
func TestRestoreMailUnitSuite(t *testing.T) {
suite.Run(t, &RestoreMailUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *RestoreMailUnitSuite) TestIsValidEmail() {
table := []struct {
name string
email string
check assert.BoolAssertionFunc
}{
{
name: "valid email",
email: "foo@bar.com",
check: assert.True,
},
{
name: "invalid email, missing domain",
email: "foo.com",
check: assert.False,
},
{
name: "invalid email, random uuid",
email: "12345678-abcd-90ef-88f8-2d95ef12fb66",
check: assert.False,
},
{
name: "empty email",
email: "",
check: assert.False,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
result := isValidEmail(test.email)
test.check(t, result)
})
}
}
func (suite *RestoreMailUnitSuite) TestIsValidDN() {
table := []struct {
name string
dn string
check assert.BoolAssertionFunc
}{
{
name: "valid DN",
dn: TestDN,
check: assert.True,
},
{
name: "invalid DN",
dn: "random string",
check: assert.False,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
result := isValidDN(test.dn)
test.check(t, result)
})
}
}
func (suite *RestoreMailUnitSuite) TestSetReplyTos() {
t := suite.T()
replyTos := make([]models.Recipientable, 0)
emailAddresses := map[string]string{
"foo.bar": "foo@bar.com",
"foo.com": "foo.com",
"empty": "",
"dn": TestDN,
}
validEmailAddresses := map[string]string{
"foo.bar": "foo@bar.com",
"dn": TestDN,
}
for k, v := range emailAddresses {
emailAddress := models.NewEmailAddress()
emailAddress.SetAddress(ptr.To(v))
emailAddress.SetName(ptr.To(k))
replyTo := models.NewRecipient()
replyTo.SetEmailAddress(emailAddress)
replyTos = append(replyTos, replyTo)
}
mailMessage := models.NewMessage()
mailMessage.SetReplyTo(replyTos)
setReplyTos(mailMessage)
sanitizedReplyTos := mailMessage.GetReplyTo()
require.Len(t, sanitizedReplyTos, len(validEmailAddresses))
for _, sanitizedReplyTo := range sanitizedReplyTos {
emailAddress := sanitizedReplyTo.GetEmailAddress()
assert.Contains(t, validEmailAddresses, ptr.Val(emailAddress.GetName()))
assert.Equal(t, validEmailAddresses[ptr.Val(emailAddress.GetName())], ptr.Val(emailAddress.GetAddress()))
}
}
var _ mailRestorer = &mailRestoreMock{} var _ mailRestorer = &mailRestoreMock{}
type mailRestoreMock struct { type mailRestoreMock struct {

View File

@ -6,10 +6,15 @@ import (
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
// ---------------------------------------------------------------------------
// get and serialize item mock
// ---------------------------------------------------------------------------
type ItemGetSerialize struct { type ItemGetSerialize struct {
GetData serialization.Parsable GetData serialization.Parsable
GetCount int GetCount int
@ -44,3 +49,23 @@ func (m *ItemGetSerialize) Serialize(
func DefaultItemGetSerialize() *ItemGetSerialize { func DefaultItemGetSerialize() *ItemGetSerialize {
return &ItemGetSerialize{} return &ItemGetSerialize{}
} }
// ---------------------------------------------------------------------------
// can skip item failure mock
// ---------------------------------------------------------------------------
type canSkipFailChecker struct {
canSkip bool
}
func (m canSkipFailChecker) CanSkipItemFailure(
err error,
resourceID string,
opts control.Options,
) (fault.SkipCause, bool) {
return fault.SkipCause("testing"), m.canSkip
}
func NeverCanSkipFailChecker() *canSkipFailChecker {
return &canSkipFailChecker{}
}

View File

@ -197,7 +197,12 @@ func (h BackupHandler[T]) AugmentItemInfo(
return h.ItemInfo return h.ItemInfo
} }
func (h *BackupHandler[T]) Get(context.Context, string, map[string]string) (*http.Response, error) { func (h *BackupHandler[T]) Get(
context.Context,
string,
map[string]string,
bool,
) (*http.Response, error) {
c := h.getCall c := h.getCall
h.getCall++ h.getCall++

View File

@ -420,6 +420,9 @@ func (suite *BackupOpUnitSuite) TestNewBackupOperation_configuredOptionsMatchInp
MaxPages: 46, MaxPages: 46,
Enabled: true, Enabled: true,
}, },
SkipEventsOnInstance503ForResources: map[string]struct{}{
"resource": {},
},
} }
t := suite.T() t := suite.T()

View File

@ -305,6 +305,10 @@ func RunIncrementalDriveishBackupTest(
itemsRead int itemsRead int
itemsWritten int itemsWritten int
nonMetaItemsWritten int nonMetaItemsWritten int
// TODO: Temporary mechanism to skip permissions
// related tests. Remove once we figure out the issue.
skipChecks bool
}{ }{
{ {
name: "clean incremental, no changes", name: "clean incremental, no changes",
@ -353,6 +357,7 @@ func RunIncrementalDriveishBackupTest(
itemsRead: 1, // .data file for newitem itemsRead: 1, // .data file for newitem
itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated) itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated)
nonMetaItemsWritten: 0, // none because the file is considered cached instead of written. nonMetaItemsWritten: 0, // none because the file is considered cached instead of written.
skipChecks: true,
}, },
{ {
name: "remove permission from new file", name: "remove permission from new file",
@ -372,6 +377,7 @@ func RunIncrementalDriveishBackupTest(
itemsRead: 1, // .data file for newitem itemsRead: 1, // .data file for newitem
itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated) itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated)
nonMetaItemsWritten: 0, // none because the file is considered cached instead of written. nonMetaItemsWritten: 0, // none because the file is considered cached instead of written.
skipChecks: true,
}, },
{ {
name: "add permission to container", name: "add permission to container",
@ -392,6 +398,7 @@ func RunIncrementalDriveishBackupTest(
itemsRead: 0, itemsRead: 0,
itemsWritten: 2, // .dirmeta for collection itemsWritten: 2, // .dirmeta for collection
nonMetaItemsWritten: 0, // no files updated as update on container nonMetaItemsWritten: 0, // no files updated as update on container
skipChecks: true,
}, },
{ {
name: "remove permission from container", name: "remove permission from container",
@ -412,6 +419,7 @@ func RunIncrementalDriveishBackupTest(
itemsRead: 0, itemsRead: 0,
itemsWritten: 2, // .dirmeta for collection itemsWritten: 2, // .dirmeta for collection
nonMetaItemsWritten: 0, // no files updated nonMetaItemsWritten: 0, // no files updated
skipChecks: true,
}, },
{ {
name: "update contents of a file", name: "update contents of a file",
@ -741,9 +749,11 @@ func RunIncrementalDriveishBackupTest(
assertReadWrite = assert.LessOrEqual assertReadWrite = assert.LessOrEqual
} }
assertReadWrite(t, expectWrites, incBO.Results.ItemsWritten, "incremental items written") if !test.skipChecks {
assertReadWrite(t, expectNonMetaWrites, incBO.Results.NonMetaItemsWritten, "incremental non-meta items written") assertReadWrite(t, expectWrites, incBO.Results.ItemsWritten, "incremental items written")
assertReadWrite(t, expectReads, incBO.Results.ItemsRead, "incremental items read") assertReadWrite(t, expectNonMetaWrites, incBO.Results.NonMetaItemsWritten, "incremental non-meta items written")
assertReadWrite(t, expectReads, incBO.Results.ItemsRead, "incremental items read")
}
assert.NoError(t, incBO.Errors.Failure(), "incremental non-recoverable error", clues.ToCore(incBO.Errors.Failure())) assert.NoError(t, incBO.Errors.Failure(), "incremental non-recoverable error", clues.ToCore(incBO.Errors.Failure()))
assert.Empty(t, incBO.Errors.Recovered(), "incremental recoverable/iteration errors") assert.Empty(t, incBO.Errors.Recovered(), "incremental recoverable/iteration errors")

View File

@ -175,7 +175,7 @@ func runGroupsIncrementalBackupTests(
suite, suite,
opts, opts,
m365.Group.ID, m365.Group.ID,
m365.User.ID, m365.SecondaryGroup.ID, // more reliable than user
path.GroupsService, path.GroupsService,
path.LibrariesCategory, path.LibrariesCategory,
ic, ic,
@ -201,8 +201,12 @@ func (suite *GroupsBackupIntgSuite) TestBackup_Run_groupsBasic() {
sel.Include( sel.Include(
selTD.GroupsBackupLibraryFolderScope(sel), selTD.GroupsBackupLibraryFolderScope(sel),
selTD.GroupsBackupChannelScope(sel), selTD.GroupsBackupChannelScope(sel))
selTD.GroupsBackupConversationScope(sel))
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
// odata.NextLink which causes an infinite loop during paging. Disabling conversations tests while
// we go fix the group mailbox.
// selTD.GroupsBackupConversationScope(sel))
bo, bod := PrepNewTestBackupOp(t, ctx, mb, sel.Selector, opts, version.Backup, counter) bo, bod := PrepNewTestBackupOp(t, ctx, mb, sel.Selector, opts, version.Backup, counter)
defer bod.Close(t, ctx) defer bod.Close(t, ctx)
@ -326,8 +330,12 @@ func (suite *GroupsBackupNightlyIntgSuite) TestBackup_Run_groupsVersion9MergeBas
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID}) sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
sel.Include( sel.Include(
selTD.GroupsBackupLibraryFolderScope(sel), selTD.GroupsBackupLibraryFolderScope(sel),
selTD.GroupsBackupChannelScope(sel), selTD.GroupsBackupChannelScope(sel))
selTD.GroupsBackupConversationScope(sel))
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
// odata.NextLink which causes an infinite loop during paging. Disabling conv backups while
// we go fix the group mailbox.
// selTD.GroupsBackupConversationScope(sel))
RunMergeBaseGroupsUpdate(suite, sel.Selector, false) RunMergeBaseGroupsUpdate(suite, sel.Selector, false)
} }
@ -336,8 +344,12 @@ func (suite *GroupsBackupNightlyIntgSuite) TestBackup_Run_groupsVersion9AssistBa
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID}) sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
sel.Include( sel.Include(
selTD.GroupsBackupLibraryFolderScope(sel), selTD.GroupsBackupLibraryFolderScope(sel),
selTD.GroupsBackupChannelScope(sel), selTD.GroupsBackupChannelScope(sel))
selTD.GroupsBackupConversationScope(sel))
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
// odata.NextLink which causes an infinite loop during paging. Disabling conv backups while
// we go fix the group mailbox.
// selTD.GroupsBackupConversationScope(sel))
RunDriveAssistBaseGroupsUpdate(suite, sel.Selector, false) RunDriveAssistBaseGroupsUpdate(suite, sel.Selector, false)
} }

View File

@ -27,6 +27,11 @@ type Options struct {
// backup data until the set limits without paying attention to what the other // backup data until the set limits without paying attention to what the other
// had already backed up. // had already backed up.
PreviewLimits PreviewItemLimits `json:"previewItemLimits"` PreviewLimits PreviewItemLimits `json:"previewItemLimits"`
// specifying a resource tuple in this map allows that resource to produce
// a Skip instead of a recoverable error in case of a failure due to 503 when
// retrieving calendar event item data.
SkipEventsOnInstance503ForResources map[string]struct{}
} }
// RateLimiter is the set of options applied to any external service facing rate // RateLimiter is the set of options applied to any external service facing rate

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/internal/observe"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/logger"
) )
func ConsumeExportCollections( func ConsumeExportCollections(
@ -19,6 +20,10 @@ func ConsumeExportCollections(
errs *fault.Bus, errs *fault.Bus,
) error { ) error {
el := errs.Local() el := errs.Local()
counted := 0
log := logger.Ctx(ctx).
With("export_location", exportLocation,
"collection_count", len(expColl))
for _, col := range expColl { for _, col := range expColl {
if el.Failure() != nil { if el.Failure() != nil {
@ -29,6 +34,13 @@ func ConsumeExportCollections(
ictx := clues.Add(ctx, "dir_name", folder) ictx := clues.Add(ctx, "dir_name", folder)
for item := range col.Items(ictx) { for item := range col.Items(ictx) {
counted++
// Log every 1000 items that are processed
if counted%1000 == 0 {
log.Infow("progress writing export items", "count_items", counted)
}
if item.Error != nil { if item.Error != nil {
el.AddRecoverable(ictx, clues.Wrap(item.Error, "getting item")) el.AddRecoverable(ictx, clues.Wrap(item.Error, "getting item"))
continue continue
@ -42,6 +54,8 @@ func ConsumeExportCollections(
} }
} }
log.Infow("completed writing export items", "count_items", counted)
return el.Failure() return el.Failure()
} }

View File

@ -12,34 +12,39 @@ type AddSkipper interface {
AddSkip(ctx context.Context, s *Skipped) AddSkip(ctx context.Context, s *Skipped)
} }
// skipCause identifies the well-known conditions to Skip an item. It is // SkipCause identifies the well-known conditions to Skip an item. It is
// important that skip cause enumerations do not overlap with general error // important that skip cause enumerations do not overlap with general error
// handling. Skips must be well known, well documented, and consistent. // handling. Skips must be well known, well documented, and consistent.
// Transient failures, undocumented or unknown conditions, and arbitrary // Transient failures, undocumented or unknown conditions, and arbitrary
// handling should never produce a skipped item. Those cases should get // handling should never produce a skipped item. Those cases should get
// handled as normal errors. // handled as normal errors.
type skipCause string type SkipCause string
const ( const (
// SkipMalware identifies a malware detection case. Files that graph // SkipMalware identifies a malware detection case. Files that graph
// api identifies as malware cannot be downloaded or uploaded, and will // api identifies as malware cannot be downloaded or uploaded, and will
// permanently fail any attempts to backup or restore. // permanently fail any attempts to backup or restore.
SkipMalware skipCause = "malware_detected" SkipMalware SkipCause = "malware_detected"
// SkipOneNote identifies that a file was skipped because it // SkipOneNote identifies that a file was skipped because it
// was a OneNote file that remains inaccessible (503 server response) // was a OneNote file that remains inaccessible (503 server response)
// regardless of the number of retries. // regardless of the number of retries.
//nolint:lll //nolint:lll
// https://support.microsoft.com/en-us/office/restrictions-and-limitations-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa#onenotenotebooks // https://support.microsoft.com/en-us/office/restrictions-and-limitations-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa#onenotenotebooks
SkipOneNote skipCause = "inaccessible_one_note_file" SkipOneNote SkipCause = "inaccessible_one_note_file"
// SkipInvalidRecipients identifies that an email was skipped because Exchange // SkipInvalidRecipients identifies that an email was skipped because Exchange
// believes it is not valid and fails any attempt to read it. // believes it is not valid and fails any attempt to read it.
SkipInvalidRecipients skipCause = "invalid_recipients_email" SkipInvalidRecipients SkipCause = "invalid_recipients_email"
// SkipCorruptData identifies that an email was skipped because graph reported // SkipCorruptData identifies that an email was skipped because graph reported
// that the email data was corrupt and failed all attempts to read it. // that the email data was corrupt and failed all attempts to read it.
SkipCorruptData skipCause = "corrupt_data" SkipCorruptData SkipCause = "corrupt_data"
// SkipKnownEventInstance503s identifies cases where we have a pre-configured list
// of event IDs where the events are known to fail with a 503 due to there being
// too many instances to retrieve from graph api.
SkipKnownEventInstance503s SkipCause = "known_event_instance_503"
) )
var _ print.Printable = &Skipped{} var _ print.Printable = &Skipped{}
@ -70,7 +75,7 @@ func (s *Skipped) String() string {
} }
// HasCause compares the underlying cause against the parameter. // HasCause compares the underlying cause against the parameter.
func (s *Skipped) HasCause(c skipCause) bool { func (s *Skipped) HasCause(c SkipCause) bool {
if s == nil { if s == nil {
return false return false
} }
@ -105,27 +110,27 @@ func (s Skipped) Values(bool) []string {
} }
// ContainerSkip produces a Container-kind Item for tracking skipped items. // ContainerSkip produces a Container-kind Item for tracking skipped items.
func ContainerSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped { func ContainerSkip(cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return itemSkip(ContainerType, cause, namespace, id, name, addtl) return itemSkip(ContainerType, cause, namespace, id, name, addtl)
} }
// EmailSkip produces a Email-kind Item for tracking skipped items. // EmailSkip produces a Email-kind Item for tracking skipped items.
func EmailSkip(cause skipCause, user, id string, addtl map[string]any) *Skipped { func EmailSkip(cause SkipCause, user, id string, addtl map[string]any) *Skipped {
return itemSkip(EmailType, cause, user, id, "", addtl) return itemSkip(EmailType, cause, user, id, "", addtl)
} }
// FileSkip produces a File-kind Item for tracking skipped items. // FileSkip produces a File-kind Item for tracking skipped items.
func FileSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped { func FileSkip(cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return itemSkip(FileType, cause, namespace, id, name, addtl) return itemSkip(FileType, cause, namespace, id, name, addtl)
} }
// OnwerSkip produces a ResourceOwner-kind Item for tracking skipped items. // OnwerSkip produces a ResourceOwner-kind Item for tracking skipped items.
func OwnerSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped { func OwnerSkip(cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return itemSkip(ResourceOwnerType, cause, namespace, id, name, addtl) return itemSkip(ResourceOwnerType, cause, namespace, id, name, addtl)
} }
// itemSkip produces a Item of the provided type for tracking skipped items. // itemSkip produces a Item of the provided type for tracking skipped items.
func itemSkip(t ItemType, cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped { func itemSkip(t ItemType, cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
return &Skipped{ return &Skipped{
Item: Item{ Item: Item{
Namespace: namespace, Namespace: namespace,

View File

@ -47,7 +47,7 @@ func (c Access) GetToken(
c.Credentials.AzureClientSecret)) c.Credentials.AzureClientSecret))
) )
resp, err := c.Post(ctx, rawURL, headers, body) resp, err := c.Post(ctx, rawURL, headers, body, false)
if err != nil { if err != nil {
return clues.Stack(err) return clues.Stack(err)
} }

View File

@ -63,7 +63,14 @@ func NewClient(
return Client{}, err return Client{}, err
} }
rqr := graph.NewNoTimeoutHTTPWrapper(counter) azureAuth, err := graph.NewAzureAuth(creds)
if err != nil {
return Client{}, clues.Wrap(err, "generating azure authorizer")
}
rqr := graph.NewNoTimeoutHTTPWrapper(
counter,
graph.AuthorizeRequester(azureAuth))
if co.DeltaPageSize < 1 || co.DeltaPageSize > maxDeltaPageSize { if co.DeltaPageSize < 1 || co.DeltaPageSize > maxDeltaPageSize {
co.DeltaPageSize = maxDeltaPageSize co.DeltaPageSize = maxDeltaPageSize
@ -124,11 +131,7 @@ func newLargeItemService(
counter *count.Bus, counter *count.Bus,
) (*graph.Service, error) { ) (*graph.Service, error) {
a, err := NewService(creds, counter, graph.NoTimeout()) a, err := NewService(creds, counter, graph.NoTimeout())
if err != nil { return a, clues.Wrap(err, "generating no-timeout graph adapter").OrNil()
return nil, clues.Wrap(err, "generating no-timeout graph adapter")
}
return a, nil
} }
type Getter interface { type Getter interface {
@ -136,6 +139,7 @@ type Getter interface {
ctx context.Context, ctx context.Context,
url string, url string,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) ) (*http.Response, error)
} }
@ -144,8 +148,9 @@ func (c Client) Get(
ctx context.Context, ctx context.Context,
url string, url string,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) { ) (*http.Response, error) {
return c.Requester.Request(ctx, http.MethodGet, url, nil, headers) return c.Requester.Request(ctx, http.MethodGet, url, nil, headers, requireAuth)
} }
// Get performs an ad-hoc get request using its graph.Requester // Get performs an ad-hoc get request using its graph.Requester
@ -154,8 +159,9 @@ func (c Client) Post(
url string, url string,
headers map[string]string, headers map[string]string,
body io.Reader, body io.Reader,
requireAuth bool,
) (*http.Response, error) { ) (*http.Response, error) {
return c.Requester.Request(ctx, http.MethodGet, url, body, headers) return c.Requester.Request(ctx, http.MethodGet, url, body, headers, requireAuth)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -186,8 +186,11 @@ func (c Contacts) GetItem(
Contacts(). Contacts().
ByContactId(itemID). ByContactId(itemID).
Get(ctx, options) Get(ctx, options)
if err != nil {
return nil, nil, clues.Stack(err)
}
return cont, ContactInfo(cont), clues.Stack(err).OrNil() return cont, ContactInfo(cont), nil
} }
func (c Contacts) PostItem( func (c Contacts) PostItem(

View File

@ -32,6 +32,9 @@ func (suite *ConversationsPagerIntgSuite) SetupSuite() {
} }
func (suite *ConversationsPagerIntgSuite) TestEnumerateConversations_withThreadsAndPosts() { func (suite *ConversationsPagerIntgSuite) TestEnumerateConversations_withThreadsAndPosts() {
// Skip
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
var ( var (
t = suite.T() t = suite.T()
ac = suite.its.ac.Conversations() ac = suite.its.ac.Conversations()

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/count" "github.com/alcionai/corso/src/pkg/count"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -107,13 +108,10 @@ func (suite *EventsPagerUnitSuite) TestEventsList() {
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(validEventsListSingleNextLinkResponse) JSON(validEventsListSingleNextLinkResponse)
// Number of retries and delay between retries is handled by a kiota
// middleware. We can change the default config parameters when setting
// up the mock in a later PR.
gock.New(graphAPIHostURL). gock.New(graphAPIHostURL).
Get(nextLinkPath). Get(nextLinkPath).
AddMatcher(pageSizeMatcher(t, expectedPageSize)). AddMatcher(pageSizeMatcher(t, expectedPageSize)).
Times(4). Times(2). // retry count is configured to 1
Reply(http.StatusServiceUnavailable). Reply(http.StatusServiceUnavailable).
BodyString(""). BodyString("").
Type("text/plain") Type("text/plain")
@ -241,7 +239,9 @@ func (suite *EventsPagerUnitSuite) TestEventsList() {
creds, err := a.M365Config() creds, err := a.M365Config()
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
client, err := gockClient(creds, count.New()) // Run with a single retry since 503 retries are exponential and
// the test will take a long time to run.
client, err := gockClient(creds, count.New(), graph.MaxRetries(1))
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
t.Cleanup(gock.Off) t.Cleanup(gock.Off)

View File

@ -0,0 +1,94 @@
package graph
import (
"context"
"net/http"
"net/url"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/alcionai/clues"
abstractions "github.com/microsoft/kiota-abstractions-go"
kauth "github.com/microsoft/kiota-authentication-azure-go"
"github.com/alcionai/corso/src/pkg/account"
)
func GetAuth(tenant, client, secret string) (*kauth.AzureIdentityAuthenticationProvider, error) {
// Client Provider: Uses Secret for access to tenant-level data
cred, err := azidentity.NewClientSecretCredential(tenant, client, secret, nil)
if err != nil {
return nil, clues.Wrap(err, "creating m365 client identity")
}
auth, err := kauth.NewAzureIdentityAuthenticationProviderWithScopes(
cred,
[]string{"https://graph.microsoft.com/.default"})
if err != nil {
return nil, clues.Wrap(err, "creating azure authentication")
}
return auth, nil
}
// ---------------------------------------------------------------------------
// requester authorization
// ---------------------------------------------------------------------------
type authorizer interface {
addAuthToHeaders(
ctx context.Context,
urlStr string,
headers http.Header,
) error
}
// consumed by kiota
type authenticateRequester interface {
AuthenticateRequest(
ctx context.Context,
request *abstractions.RequestInformation,
additionalAuthenticationContext map[string]any,
) error
}
// ---------------------------------------------------------------------------
// Azure Authorizer
// ---------------------------------------------------------------------------
type azureAuth struct {
auth authenticateRequester
}
func NewAzureAuth(creds account.M365Config) (*azureAuth, error) {
auth, err := GetAuth(
creds.AzureTenantID,
creds.AzureClientID,
creds.AzureClientSecret)
return &azureAuth{auth}, clues.Stack(err).OrNil()
}
func (aa azureAuth) addAuthToHeaders(
ctx context.Context,
urlStr string,
headers http.Header,
) error {
requestInfo := abstractions.NewRequestInformation()
uri, err := url.Parse(urlStr)
if err != nil {
return clues.WrapWC(ctx, err, "parsing url").OrNil()
}
requestInfo.SetUri(*uri)
err = aa.auth.AuthenticateRequest(ctx, requestInfo, nil)
for _, k := range requestInfo.Headers.ListKeys() {
for _, v := range requestInfo.Headers.Get(k) {
headers.Add(k, v)
}
}
return clues.WrapWC(ctx, err, "authorizing request").OrNil()
}

View File

@ -240,7 +240,7 @@ func (mw *RateLimiterMiddleware) Intercept(
middlewareIndex int, middlewareIndex int,
req *http.Request, req *http.Request,
) (*http.Response, error) { ) (*http.Response, error) {
QueueRequest(req.Context()) QueueRequest(getReqCtx(req))
return pipeline.Next(req, middlewareIndex) return pipeline.Next(req, middlewareIndex)
} }
@ -339,7 +339,7 @@ func (mw *throttlingMiddleware) Intercept(
middlewareIndex int, middlewareIndex int,
req *http.Request, req *http.Request,
) (*http.Response, error) { ) (*http.Response, error) {
err := mw.tf.Block(req.Context()) err := mw.tf.Block(getReqCtx(req))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -114,7 +114,14 @@ const (
// ErrServiceUnavailableEmptyResp indicates the remote service returned a 503 // ErrServiceUnavailableEmptyResp indicates the remote service returned a 503
// with an empty response body. This can sometimes happen if a request times out // with an empty response body. This can sometimes happen if a request times out
// during processing. // during processing.
var ErrServiceUnavailableEmptyResp = clues.New("service unavailable and no returned content") //
// TODO(ashmrtn): Either make a separate error struct for empty responses and
// implement Is() on it or start using tags on errors for the different status
// codes.
var (
ErrServiceUnavailableEmptyResp = clues.New("service unavailable and no returned content")
ErrNotFoundEmptyResp = clues.New("not found and no returned content")
)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// error categorization // error categorization
@ -149,7 +156,8 @@ func stackWithCoreErr(ctx context.Context, err error, traceDepth int) error {
labels = append(labels, core.LabelRootCauseUnknown) labels = append(labels, core.LabelRootCauseUnknown)
} }
stacked := stackWithDepth(ctx, err, 1+traceDepth) stacked := stackWithDepth(ctx, err, 1+traceDepth).
Label(LabelStatus(ode.Resp.StatusCode))
// labeling here because we want the context from stackWithDepth first // labeling here because we want the context from stackWithDepth first
for _, label := range labels { for _, label := range labels {
@ -410,9 +418,14 @@ func stackReq(
// then all we get from graph SDK is an error saying "content is empty" which // then all we get from graph SDK is an error saying "content is empty" which
// isn't particularly useful. // isn't particularly useful.
if resp != nil && if resp != nil &&
resp.ContentLength == 0 && resp.ContentLength == 0 {
resp.StatusCode == http.StatusServiceUnavailable { switch resp.StatusCode {
e = clues.Stack(ErrServiceUnavailableEmptyResp, e) case http.StatusServiceUnavailable:
e = clues.Stack(ErrServiceUnavailableEmptyResp, e)
case http.StatusNotFound:
e = clues.Stack(ErrNotFoundEmptyResp, e)
}
} }
if e == nil { if e == nil {
@ -688,10 +701,48 @@ func (ode oDataErr) errMessageMatchesAllFilters(err error, fs ...filters.Filter)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// other helpers // other helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const (
// JWTQueryParam is a query param embed in graph download URLs which holds
// JWT token.
JWTQueryParam = "tempauth"
// base64 encoded json header. Contains {"alg":"HS256","typ":"JWT"}
//
// Hardcoding this instead of generating it every time on the fly.
// The algorithm doesn't matter as we are not verifying the token.
jwtHeader = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
)
// JWTQueryParam is a query param embed in graph download URLs which holds func sanitizeToken(rawToken string) string {
// JWT token. segments := strings.Split(rawToken, ".")
const JWTQueryParam = "tempauth"
// Check if the token has the old format, in which it has 3 segments and
// conforms to jwt spec. Format is seg1.seg2.seg3.
if len(segments) == 3 {
return rawToken
}
// Check if it is a msft proprietary token in which it has 4 segments and
// doesn't meet jwt spec. Format is v1.seg1.seg2.seg3. Return a token which
// meets jwt spec.
//
// In this proprietary token, there is no jwt header segment. Also, the claims
// section is split into first and segments. The first segment contains the
// `exp` claim that we are interested in.
//
// The second segment contains the rest of the claims, but likely encrypted.
// We don't need it so discard it. The last segment contains the signature which
// we don't care about either, as we are not verifying the token. So append it as is.
//
// It's okay if the sanitized token still doesn't meet jwt spec. It'll fail decoding
// later and we have fallbacks for that.
if len(segments) == 4 && segments[0] == "v1" {
return jwtHeader + "." + segments[1] + "." + segments[3]
}
// If MSFT change the token format again on us, just return empty string and let caller
// handle it as an error.
return ""
}
// IsURLExpired inspects the jwt token embed in the item download url // IsURLExpired inspects the jwt token embed in the item download url
// and returns true if it is expired. // and returns true if it is expired.
@ -702,12 +753,20 @@ func IsURLExpired(
expiredErr error, expiredErr error,
err error, err error,
) { ) {
ctx = clues.Add(ctx, "checked_url", urlStr)
// Extract the raw JWT string from the download url. // Extract the raw JWT string from the download url.
rawJWT, err := common.GetQueryParamFromURL(urlStr, JWTQueryParam) rawJWT, err := common.GetQueryParamFromURL(urlStr, JWTQueryParam)
if err != nil { if err != nil {
return nil, clues.WrapWC(ctx, err, "jwt query param not found") return nil, clues.WrapWC(ctx, err, "jwt query param not found")
} }
// Token may have a proprietary format. Try to sanitize it to jwt format.
rawJWT = sanitizeToken(rawJWT)
if len(rawJWT) == 0 {
return nil, clues.WrapWC(ctx, err, "sanitizing jwt")
}
expired, err := jwt.IsJWTExpired(rawJWT) expired, err := jwt.IsJWTExpired(rawJWT)
if err != nil { if err != nil {
return nil, clues.WrapWC(ctx, err, "checking jwt expiry") return nil, clues.WrapWC(ctx, err, "checking jwt expiry")

View File

@ -36,6 +36,7 @@ type Requester interface {
method, url string, method, url string,
body io.Reader, body io.Reader,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) ) (*http.Response, error)
} }
@ -58,12 +59,8 @@ func NewHTTPWrapper(
transport: defaultTransport(), transport: defaultTransport(),
}, },
} }
redirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
hc = &http.Client{ hc = &http.Client{
CheckRedirect: redirect, Transport: rt,
Transport: rt,
} }
) )
@ -100,6 +97,7 @@ func (hw httpWrapper) Request(
method, url string, method, url string,
body io.Reader, body io.Reader,
headers map[string]string, headers map[string]string,
requireAuth bool,
) (*http.Response, error) { ) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, url, body) req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil { if err != nil {
@ -115,6 +113,17 @@ func (hw httpWrapper) Request(
// See https://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online#how-to-decorate-your-http-traffic // See https://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online#how-to-decorate-your-http-traffic
req.Header.Set("User-Agent", "ISV|Alcion|Corso/"+version.Version) req.Header.Set("User-Agent", "ISV|Alcion|Corso/"+version.Version)
if requireAuth {
if hw.config.requesterAuth == nil {
return nil, clues.Wrap(err, "http wrapper misconfigured: missing required authorization")
}
err := hw.config.requesterAuth.addAuthToHeaders(ctx, url, req.Header)
if err != nil {
return nil, clues.Wrap(err, "setting request auth headers")
}
}
retriedErrors := []string{} retriedErrors := []string{}
var e error var e error
@ -137,7 +146,7 @@ func (hw httpWrapper) Request(
resp, err := hw.client.Do(req) resp, err := hw.client.Do(req)
if err == nil { if err == nil {
logResp(ictx, resp) logResp(ictx, resp, req)
return resp, nil return resp, nil
} }

View File

@ -40,9 +40,10 @@ func (suite *HTTPWrapperIntgSuite) TestNewHTTPWrapper() {
resp, err := hw.Request( resp, err := hw.Request(
ctx, ctx,
http.MethodGet, http.MethodGet,
"https://www.corsobackup.io", "https://www.google.com",
nil, nil,
nil) nil,
false)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
defer resp.Body.Close() defer resp.Body.Close()
@ -76,6 +77,56 @@ func (mw *mwForceResp) Intercept(
return mw.resp, mw.err return mw.resp, mw.err
} }
func (suite *HTTPWrapperIntgSuite) TestHTTPWrapper_Request_withAuth() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
a := tconfig.NewM365Account(t)
m365, err := a.M365Config()
require.NoError(t, err, clues.ToCore(err))
azureAuth, err := NewAzureAuth(m365)
require.NoError(t, err, clues.ToCore(err))
hw := NewHTTPWrapper(count.New(), AuthorizeRequester(azureAuth))
// any request that requires authorization will do
resp, err := hw.Request(
ctx,
http.MethodGet,
"https://graph.microsoft.com/v1.0/users",
nil,
nil,
true)
require.NoError(t, err, clues.ToCore(err))
defer resp.Body.Close()
require.NotNil(t, resp)
require.Equal(t, http.StatusOK, resp.StatusCode)
// also validate that non-auth'd endpoints succeed
resp, err = hw.Request(
ctx,
http.MethodGet,
"https://www.google.com",
nil,
nil,
true)
require.NoError(t, err, clues.ToCore(err))
defer resp.Body.Close()
require.NotNil(t, resp)
require.Equal(t, http.StatusOK, resp.StatusCode)
}
// ---------------------------------------------------------------------------
// unit
// ---------------------------------------------------------------------------
type HTTPWrapperUnitSuite struct { type HTTPWrapperUnitSuite struct {
tester.Suite tester.Suite
} }
@ -84,26 +135,25 @@ func TestHTTPWrapperUnitSuite(t *testing.T) {
suite.Run(t, &HTTPWrapperUnitSuite{Suite: tester.NewUnitSuite(t)}) suite.Run(t, &HTTPWrapperUnitSuite{Suite: tester.NewUnitSuite(t)})
} }
func (suite *HTTPWrapperUnitSuite) TestNewHTTPWrapper_redirectMiddleware() { func (suite *HTTPWrapperUnitSuite) TestHTTPWrapper_Request_redirect() {
t := suite.T() t := suite.T()
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
url := "https://graph.microsoft.com/fnords/beaux/regard" respHdr := http.Header{}
respHdr.Set("Location", "localhost:99999999/smarfs")
hdr := http.Header{}
hdr.Set("Location", "localhost:99999999/smarfs")
toResp := &http.Response{ toResp := &http.Response{
StatusCode: http.StatusFound, StatusCode: http.StatusFound,
Header: hdr, Header: respHdr,
} }
mwResp := mwForceResp{ mwResp := mwForceResp{
resp: toResp, resp: toResp,
alternate: func(req *http.Request) (bool, *http.Response, error) { alternate: func(req *http.Request) (bool, *http.Response, error) {
if strings.HasSuffix(req.URL.String(), "smarfs") { if strings.HasSuffix(req.URL.String(), "smarfs") {
assert.Equal(t, req.Header.Get("X-Test-Val"), "should-be-copied-to-redirect")
return true, &http.Response{StatusCode: http.StatusOK}, nil return true, &http.Response{StatusCode: http.StatusOK}, nil
} }
@ -113,17 +163,22 @@ func (suite *HTTPWrapperUnitSuite) TestNewHTTPWrapper_redirectMiddleware() {
hw := NewHTTPWrapper(count.New(), appendMiddleware(&mwResp)) hw := NewHTTPWrapper(count.New(), appendMiddleware(&mwResp))
resp, err := hw.Request(ctx, http.MethodGet, url, nil, nil) resp, err := hw.Request(
ctx,
http.MethodGet,
"https://graph.microsoft.com/fnords/beaux/regard",
nil,
map[string]string{"X-Test-Val": "should-be-copied-to-redirect"},
false)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
defer resp.Body.Close() defer resp.Body.Close()
require.NotNil(t, resp) require.NotNil(t, resp)
// require.Equal(t, 1, calledCorrectly, "test server was called with expected path")
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
} }
func (suite *HTTPWrapperUnitSuite) TestNewHTTPWrapper_http2StreamErrorRetries() { func (suite *HTTPWrapperUnitSuite) TestHTTPWrapper_Request_http2StreamErrorRetries() {
var ( var (
url = "https://graph.microsoft.com/fnords/beaux/regard" url = "https://graph.microsoft.com/fnords/beaux/regard"
streamErr = http2.StreamError{ streamErr = http2.StreamError{
@ -188,7 +243,7 @@ func (suite *HTTPWrapperUnitSuite) TestNewHTTPWrapper_http2StreamErrorRetries()
// the test middleware. // the test middleware.
hw.retryDelay = 0 hw.retryDelay = 0
_, err := hw.Request(ctx, http.MethodGet, url, nil, nil) _, err := hw.Request(ctx, http.MethodGet, url, nil, nil, false)
require.ErrorAs(t, err, &http2.StreamError{}, clues.ToCore(err)) require.ErrorAs(t, err, &http2.StreamError{}, clues.ToCore(err))
require.Equal(t, test.expectRetries, tries, "count of retries") require.Equal(t, test.expectRetries, tries, "count of retries")
}) })

View File

@ -5,7 +5,13 @@ import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os" "os"
"strings"
"time"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/common/jwt"
"github.com/alcionai/corso/src/internal/common/pii"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
) )
@ -25,7 +31,7 @@ func shouldLogRespBody(resp *http.Response) bool {
resp.StatusCode > 399 resp.StatusCode > 399
} }
func logResp(ctx context.Context, resp *http.Response) { func logResp(ctx context.Context, resp *http.Response, req *http.Request) {
var ( var (
log = logger.Ctx(ctx) log = logger.Ctx(ctx)
respClass = resp.StatusCode / 100 respClass = resp.StatusCode / 100
@ -42,6 +48,25 @@ func logResp(ctx context.Context, resp *http.Response) {
return return
} }
// Log bearer token iat and exp claims if we hit 401s. This is purely for
// debugging purposes and will be removed in the future.
if resp.StatusCode == http.StatusUnauthorized {
errs := []any{"graph api error: " + resp.Status}
// As per MSFT docs, the token may have a special format and may not always
// validate as a JWT. Hence log token lifetime in a best effort manner only.
iat, exp, err := getTokenLifetime(ctx, req)
if err != nil {
errs = append(errs, " getting token lifetime: ", err)
}
log.With("response", getRespDump(ctx, resp, logBody)).
With("token issued at", iat, "token expires at", exp).
Error(errs...)
return
}
// Log api calls according to api debugging configurations. // Log api calls according to api debugging configurations.
switch respClass { switch respClass {
case 2: case 2:
@ -69,3 +94,51 @@ func getRespDump(ctx context.Context, resp *http.Response, getBody bool) string
return string(respDump) return string(respDump)
} }
func getReqCtx(req *http.Request) context.Context {
if req == nil {
return context.Background()
}
var logURL pii.SafeURL
if req.URL != nil {
logURL = LoggableURL(req.URL.String())
}
return clues.AddTraceName(
req.Context(),
"graph-http-middleware",
"method", req.Method,
"url", logURL,
"request_content_len", req.ContentLength)
}
// GetTokenLifetime extracts the JWT token embedded in the request and returns
// the token's issue and expiration times. The token is expected to be in the
// "Authorization" header, with a "Bearer " prefix. If the token is not present
// or is malformed, an error is returned.
func getTokenLifetime(
ctx context.Context,
req *http.Request,
) (time.Time, time.Time, error) {
if req == nil {
return time.Time{}, time.Time{}, clues.New("nil request")
}
// Don't throw an error if auth header is absent. This is to prevent
// unnecessary noise in the logs for requests served by the http requestor
// client. These requests may be preauthenticated and may not carry auth headers.
rawToken := req.Header.Get("Authorization")
if len(rawToken) == 0 {
return time.Time{}, time.Time{}, nil
}
// Strip the "Bearer " prefix from the token. This prefix is guaranteed to be
// present as per msft docs. But even if it's not, the jwt lib will handle
// malformed tokens gracefully and return an error.
rawToken = strings.TrimPrefix(rawToken, "Bearer ")
iat, exp, err := jwt.GetJWTLifetime(ctx, rawToken)
return iat, exp, clues.Stack(err).OrNil()
}

View File

@ -125,15 +125,12 @@ func (mw *LoggingMiddleware) Intercept(
} }
ctx := clues.Add( ctx := clues.Add(
req.Context(), getReqCtx(req),
"method", req.Method,
"url", LoggableURL(req.URL.String()),
"request_content_len", req.ContentLength,
"resp_status", resp.Status, "resp_status", resp.Status,
"resp_status_code", resp.StatusCode, "resp_status_code", resp.StatusCode,
"resp_content_len", resp.ContentLength) "resp_content_len", resp.ContentLength)
logResp(ctx, resp) logResp(ctx, resp, req)
return resp, err return resp, err
} }
@ -156,7 +153,7 @@ func (mw RetryMiddleware) Intercept(
middlewareIndex int, middlewareIndex int,
req *http.Request, req *http.Request,
) (*http.Response, error) { ) (*http.Response, error) {
ctx := req.Context() ctx := getReqCtx(req)
resp, err := pipeline.Next(req, middlewareIndex) resp, err := pipeline.Next(req, middlewareIndex)
retriable := IsErrTimeout(err) || retriable := IsErrTimeout(err) ||
@ -235,7 +232,11 @@ func (mw RetryMiddleware) retryRequest(
case <-ctx.Done(): case <-ctx.Done():
// Don't retry if the context is marked as done, it will just error out // Don't retry if the context is marked as done, it will just error out
// when we attempt to send the retry anyway. // when we attempt to send the retry anyway.
return resp, clues.StackWC(ctx, ctx.Err()) err := clues.StackWC(ctx, ctx.Err())
logger.CtxErr(ctx, err).Info("request context marked done")
return resp, err
case <-timer.C: case <-timer.C:
} }
@ -249,7 +250,9 @@ func (mw RetryMiddleware) retryRequest(
return resp, Wrap(ctx, err, "resetting request body reader") return resp, Wrap(ctx, err, "resetting request body reader")
} }
} else { } else {
logger.Ctx(ctx).Error("body is not an io.Seeker: unable to reset request body") logger.
Ctx(getReqCtx(req)).
Error("body is not an io.Seeker: unable to reset request body")
} }
} }

View File

@ -505,3 +505,95 @@ func (suite *MiddlewareUnitSuite) TestLimiterConsumption() {
}) })
} }
} }
const (
// Raw test token valid for 100 years.
rawToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
"eyJuYmYiOiIxNjkxODE5NTc5IiwiZXhwIjoiMzk0NTUyOTE3OSIsImVuZHBvaW50dXJsTGVuZ3RoIjoiMTYw" +
"IiwiaXNsb29wYmFjayI6IlRydWUiLCJ2ZXIiOiJoYXNoZWRwcm9vZnRva2VuIiwicm9sZXMiOiJhbGxmaWxl" +
"cy53cml0ZSBhbGxzaXRlcy5mdWxsY29udHJvbCBhbGxwcm9maWxlcy5yZWFkIiwidHQiOiIxIiwiYWxnIjoi" +
"SFMyNTYifQ" +
".signature"
)
// Tests getTokenLifetime
func (suite *MiddlewareUnitSuite) TestGetTokenLifetime() {
table := []struct {
name string
request *http.Request
expectErr assert.ErrorAssertionFunc
}{
{
name: "nil request",
request: nil,
expectErr: assert.Error,
},
// Test that we don't throw an error if auth header is absent.
// This is to prevent unnecessary noise in logs for requestor http client.
{
name: "no authorization header",
request: &http.Request{
Header: http.Header{},
},
expectErr: assert.NoError,
},
{
name: "well formed auth header with token",
request: &http.Request{
Header: http.Header{
"Authorization": []string{"Bearer " + rawToken},
},
},
expectErr: assert.NoError,
},
{
name: "Missing Bearer prefix but valid token",
request: &http.Request{
Header: http.Header{
"Authorization": []string{rawToken},
},
},
expectErr: assert.NoError,
},
{
name: "invalid token",
request: &http.Request{
Header: http.Header{
"Authorization": []string{"Bearer " + "invalid"},
},
},
expectErr: assert.Error,
},
{
name: "valid prefix but empty token",
request: &http.Request{
Header: http.Header{
"Authorization": []string{"Bearer "},
},
},
expectErr: assert.Error,
},
{
name: "Invalid prefix but valid token",
request: &http.Request{
Header: http.Header{
"Authorization": []string{"Bearer" + rawToken},
},
},
expectErr: assert.Error,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
// iat, exp specific tests are in jwt package.
_, _, err := getTokenLifetime(ctx, test.request)
test.expectErr(t, err, clues.ToCore(err))
})
}
}

View File

@ -2,14 +2,13 @@ package graph
import ( import (
"context" "context"
"io"
"net/http" "net/http"
"time" "time"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/alcionai/clues" "github.com/alcionai/clues"
abstractions "github.com/microsoft/kiota-abstractions-go" abstractions "github.com/microsoft/kiota-abstractions-go"
"github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoft/kiota-abstractions-go/serialization"
kauth "github.com/microsoft/kiota-authentication-azure-go"
khttp "github.com/microsoft/kiota-http-go" khttp "github.com/microsoft/kiota-http-go"
msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go" msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go"
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core" msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
@ -126,23 +125,6 @@ func CreateAdapter(
return wrapAdapter(adpt, cc), nil return wrapAdapter(adpt, cc), nil
} }
func GetAuth(tenant string, client string, secret string) (*kauth.AzureIdentityAuthenticationProvider, error) {
// Client Provider: Uses Secret for access to tenant-level data
cred, err := azidentity.NewClientSecretCredential(tenant, client, secret, nil)
if err != nil {
return nil, clues.Wrap(err, "creating m365 client identity")
}
auth, err := kauth.NewAzureIdentityAuthenticationProviderWithScopes(
cred,
[]string{"https://graph.microsoft.com/.default"})
if err != nil {
return nil, clues.Wrap(err, "creating azure authentication")
}
return auth, nil
}
// KiotaHTTPClient creates a httpClient with middlewares and timeout configured // KiotaHTTPClient creates a httpClient with middlewares and timeout configured
// for use in the graph adapter. // for use in the graph adapter.
// //
@ -173,7 +155,15 @@ func KiotaHTTPClient(
const ( const (
defaultDelay = 3 * time.Second defaultDelay = 3 * time.Second
defaultHTTPClientTimeout = 1 * time.Hour defaultHTTPClientTimeout = 1 * time.Hour
defaultMaxRetries = 3
// Default retry count for retry middlewares
defaultMaxRetries = 3
// Retry count for graph adapter
//
// Bumping retries to 6 since we have noticed that auth token expiry errors
// may continue to persist even after 3 retries.
adapterMaxRetries = 6
// FIXME: This should ideally be 0, but if we set to 0, graph // FIXME: This should ideally be 0, but if we set to 0, graph
// client with automatically set the context timeout to 0 as // client with automatically set the context timeout to 0 as
// well which will make the client unusable. // well which will make the client unusable.
@ -191,6 +181,11 @@ type clientConfig struct {
maxRetries int maxRetries int
// The minimum delay in seconds between retries // The minimum delay in seconds between retries
minDelay time.Duration minDelay time.Duration
// requesterAuth sets the authorization step for requester-compliant clients.
// if non-nil, it will ensure calls are authorized before querying.
// does not get consumed by the standard graph client, which already comes
// packaged with an auth protocol.
requesterAuth authorizer
appendMiddleware []khttp.Middleware appendMiddleware []khttp.Middleware
} }
@ -200,7 +195,7 @@ type Option func(*clientConfig)
// populate constructs a clientConfig according to the provided options. // populate constructs a clientConfig according to the provided options.
func populateConfig(opts ...Option) *clientConfig { func populateConfig(opts ...Option) *clientConfig {
cc := clientConfig{ cc := clientConfig{
maxConnectionRetries: defaultMaxRetries, maxConnectionRetries: adapterMaxRetries,
maxRetries: defaultMaxRetries, maxRetries: defaultMaxRetries,
minDelay: defaultDelay, minDelay: defaultDelay,
timeout: defaultHTTPClientTimeout, timeout: defaultHTTPClientTimeout,
@ -278,6 +273,12 @@ func MaxConnectionRetries(max int) Option {
} }
} }
func AuthorizeRequester(a authorizer) Option {
return func(c *clientConfig) {
c.requesterAuth = a
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Middleware Control // Middleware Control
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -373,9 +374,11 @@ func wrapAdapter(gra *msgraphsdkgo.GraphRequestAdapter, cc *clientConfig) *adapt
} }
} }
var connectionEnded = filters.Contains([]string{ // Graph may abruptly close connections, which we should retry.
var connectionEnded = filters.In([]string{
"connection reset by peer", "connection reset by peer",
"client connection force closed", "client connection force closed",
"read: connection timed out",
}) })
func (aw *adapterWrap) Send( func (aw *adapterWrap) Send(
@ -416,7 +419,9 @@ func (aw *adapterWrap) Send(
err = stackWithCoreErr(ictx, err, 1) err = stackWithCoreErr(ictx, err, 1)
e = err e = err
if IsErrConnectionReset(err) || connectionEnded.Compare(err.Error()) { if IsErrConnectionReset(err) ||
connectionEnded.Compare(err.Error()) ||
errors.Is(err, io.ErrUnexpectedEOF) {
logger.Ctx(ictx).Debug("http connection error") logger.Ctx(ictx).Debug("http connection error")
events.Inc(events.APICall, "connectionerror") events.Inc(events.APICall, "connectionerror")
} else if errors.Is(err, core.ErrAuthTokenExpired) { } else if errors.Is(err, core.ErrAuthTokenExpired) {
@ -429,6 +434,13 @@ func (aw *adapterWrap) Send(
// to limit the scope of this fix. // to limit the scope of this fix.
logger.Ctx(ictx).Debug("invalid request") logger.Ctx(ictx).Debug("invalid request")
events.Inc(events.APICall, "invalidgetrequest") events.Inc(events.APICall, "invalidgetrequest")
} else if requestInfo.Method.String() == http.MethodGet && errors.Is(err, ErrNotFoundEmptyResp) {
// We've started seeing 404s with no content being returned for messages
// message attachments, and events. Attempting to manually fetch the items
// succeeds. Therefore we want to retry these to see if we can work around
// the problem.
logger.Ctx(ictx).Debug("404 with no content")
events.Inc(events.APICall, "notfoundnocontent")
} else { } else {
// exit most errors without retry // exit most errors without retry
break break

View File

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/h2non/gock"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -26,6 +27,115 @@ import (
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata" graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
) )
// ---------------------------------------------------------------------------
// Unit tests
// ---------------------------------------------------------------------------
type GraphUnitSuite struct {
tester.Suite
}
func TestGraphUnitSuite(t *testing.T) {
suite.Run(t, &GraphUnitSuite{
Suite: tester.NewUnitSuite(t),
})
}
func (suite *GraphUnitSuite) TestNoRetryPostNoContent404() {
const (
host = "https://graph.microsoft.com"
retries = 3
)
t := suite.T()
ctx, flush := tester.NewContext(t)
t.Cleanup(flush)
a := tconfig.NewFakeM365Account(t)
creds, err := a.M365Config()
require.NoError(t, err, clues.ToCore(err))
// Run with a single retry since 503 retries are exponential and
// the test will take a long time to run.
service, err := NewGockService(
creds,
count.New(),
MaxRetries(1),
MaxConnectionRetries(retries))
require.NoError(t, err, clues.ToCore(err))
t.Cleanup(gock.Off)
gock.New(host).
Post("/v1.0/users").
Reply(http.StatusNotFound).
BodyString("").
Type("text/plain")
// Since we're retrying all 404s with no content the endpoint we use doesn't
// matter.
_, err = service.Client().Users().Post(ctx, models.NewUser(), nil)
assert.ErrorIs(t, err, ErrNotFoundEmptyResp)
assert.False(t, gock.IsPending(), "some requests not seen")
}
func (suite *GraphUnitSuite) TestRetryGetNoContent404() {
const (
host = "https://graph.microsoft.com"
retries = 3
emptyUserList = `{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
"value": []
}`
)
t := suite.T()
ctx, flush := tester.NewContext(t)
t.Cleanup(flush)
a := tconfig.NewFakeM365Account(t)
creds, err := a.M365Config()
require.NoError(t, err, clues.ToCore(err))
// Run with a single retry since 503 retries are exponential and
// the test will take a long time to run.
service, err := NewGockService(
creds,
count.New(),
MaxRetries(1),
MaxConnectionRetries(retries))
require.NoError(t, err, clues.ToCore(err))
t.Cleanup(gock.Off)
gock.New(host).
Get("/v1.0/users").
Times(retries - 1).
Reply(http.StatusNotFound).
BodyString("").
Type("text/plain")
gock.New(host).
Get("/v1.0/users").
Reply(http.StatusOK).
JSON(emptyUserList)
// Since we're retrying all 404s with no content the endpoint we use doesn't
// matter.
_, err = service.Client().Users().Get(ctx, nil)
assert.NoError(t, err, clues.ToCore(err))
assert.False(t, gock.IsPending(), "some requests not seen")
}
// ---------------------------------------------------------------------------
// Integration tests
// ---------------------------------------------------------------------------
type GraphIntgSuite struct { type GraphIntgSuite struct {
tester.Suite tester.Suite
fakeCredentials account.M365Config fakeCredentials account.M365Config
@ -93,7 +203,7 @@ func (suite *GraphIntgSuite) TestHTTPClient() {
checkConfig: func(t *testing.T, c *clientConfig) { checkConfig: func(t *testing.T, c *clientConfig) {
assert.Equal(t, defaultDelay, c.minDelay, "default delay") assert.Equal(t, defaultDelay, c.minDelay, "default delay")
assert.Equal(t, defaultMaxRetries, c.maxRetries, "max retries") assert.Equal(t, defaultMaxRetries, c.maxRetries, "max retries")
assert.Equal(t, defaultMaxRetries, c.maxConnectionRetries, "max connection retries") assert.Equal(t, adapterMaxRetries, c.maxConnectionRetries, "max connection retries")
}, },
}, },
{ {
@ -221,43 +331,92 @@ func (suite *GraphIntgSuite) TestAdapterWrap_catchesPanic() {
require.Contains(t, err.Error(), "panic", clues.ToCore(err)) require.Contains(t, err.Error(), "panic", clues.ToCore(err))
} }
func (suite *GraphIntgSuite) TestAdapterWrap_retriesConnectionClose() { func (suite *GraphIntgSuite) TestAdapterWrap_retriesConnectionInterruptions() {
t := suite.T() table := []struct {
name string
ctx, flush := tester.NewContext(t) providedErr error
defer flush() expectRetryCount int
expectErr assert.ErrorAssertionFunc
retryInc := 0 }{
{
// the panics should get caught and returned as errors name: "ECONNRESET",
alwaysECONNRESET := mwForceResp{ providedErr: syscall.ECONNRESET,
err: syscall.ECONNRESET, expectRetryCount: 7,
alternate: func(req *http.Request) (bool, *http.Response, error) { expectErr: assert.Error,
retryInc++ },
return false, nil, nil {
name: "connection reset by peer",
providedErr: clues.New("connection reset by peer what is love"),
expectRetryCount: 7,
expectErr: assert.Error,
},
{
name: "read: connection timed out",
providedErr: clues.New("read: connection timed out baby don't hurt me"),
expectRetryCount: 7,
expectErr: assert.Error,
},
{
name: "unexpected EOF",
providedErr: io.ErrUnexpectedEOF,
expectRetryCount: 7,
expectErr: assert.Error,
},
{
name: "nil error",
providedErr: nil,
expectRetryCount: 1,
expectErr: assert.NoError,
},
{
name: "non retriable error",
providedErr: clues.New("no more"),
expectRetryCount: 1,
expectErr: assert.Error,
}, },
} }
adpt, err := CreateAdapter( for _, test := range table {
suite.credentials.AzureTenantID, suite.Run(test.name, func() {
suite.credentials.AzureClientID, t := suite.T()
suite.credentials.AzureClientSecret,
count.New(),
appendMiddleware(&alwaysECONNRESET),
// Configure retry middlewares so that they don't retry on connection reset.
// Those middlewares have their own tests to verify retries.
MaxRetries(-1))
require.NoError(t, err, clues.ToCore(err))
// Retry delay doesn't really matter here since all requests will be intercepted ctx, flush := tester.NewContext(t)
// by the test middleware. Set it to 0 to reduce test runtime. defer flush()
aw := adpt.(*adapterWrap)
aw.retryDelay = 0
// the query doesn't matter retryInc := 0
_, err = NewService(adpt).Client().Users().Get(ctx, nil) forceErrMW := mwForceResp{
require.ErrorIs(t, err, syscall.ECONNRESET, clues.ToCore(err)) err: test.providedErr,
require.Equal(t, 4, retryInc, "number of retries") // send some dummy response, because kiota retry handler panics
// if err is nil and resp is nil.
resp: &http.Response{},
alternate: func(req *http.Request) (bool, *http.Response, error) {
retryInc++
return false, nil, nil
},
}
adpt, err := CreateAdapter(
suite.credentials.AzureTenantID,
suite.credentials.AzureClientID,
suite.credentials.AzureClientSecret,
count.New(),
appendMiddleware(&forceErrMW),
// Configure retry middlewares so that they don't retry on connection reset.
// Those middlewares have their own tests to verify retries.
MaxRetries(-1))
require.NoError(t, err, clues.ToCore(err))
// Retry delay doesn't really matter here since all requests will be intercepted
// by the test middleware. Set it to 0 to reduce test runtime.
aw := adpt.(*adapterWrap)
aw.retryDelay = 0
// the query doesn't matter
_, err = NewService(adpt).Client().Users().Get(ctx, nil)
test.expectErr(t, err, clues.ToCore(err))
require.Equal(t, test.expectRetryCount, retryInc, "number of retries")
})
}
} }
func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() { func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() {
@ -315,14 +474,14 @@ func (suite *GraphIntgSuite) TestAdapterWrap_retriesBadJWTToken() {
NewItemCalendarsItemEventsDeltaRequestBuilder("https://graph.microsoft.com/fnords/beaux/regard", adpt). NewItemCalendarsItemEventsDeltaRequestBuilder("https://graph.microsoft.com/fnords/beaux/regard", adpt).
Get(ctx, nil) Get(ctx, nil)
assert.ErrorIs(t, err, core.ErrAuthTokenExpired, clues.ToCore(err)) assert.ErrorIs(t, err, core.ErrAuthTokenExpired, clues.ToCore(err))
assert.Equal(t, 4, retryInc, "number of retries") assert.Equal(t, adapterMaxRetries+1, retryInc, "number of retries")
retryInc = 0 retryInc = 0
// the query doesn't matter // the query doesn't matter
_, err = NewService(adpt).Client().Users().Get(ctx, nil) _, err = NewService(adpt).Client().Users().Get(ctx, nil)
assert.ErrorIs(t, err, core.ErrAuthTokenExpired, clues.ToCore(err)) assert.ErrorIs(t, err, core.ErrAuthTokenExpired, clues.ToCore(err))
assert.Equal(t, 4, retryInc, "number of retries") assert.Equal(t, adapterMaxRetries+1, retryInc, "number of retries")
} }
// TestAdapterWrap_retriesInvalidRequest tests adapter retries for graph 400 // TestAdapterWrap_retriesInvalidRequest tests adapter retries for graph 400
@ -398,7 +557,7 @@ func (suite *GraphIntgSuite) TestAdapterWrap_retriesInvalidRequest() {
_, err = NewService(adpt).Client().Users().Get(ctx, nil) _, err = NewService(adpt).Client().Users().Get(ctx, nil)
return err return err
}, },
expectedRetries: 4, expectedRetries: adapterMaxRetries + 1,
}, },
{ {
name: "POST request, no retry", name: "POST request, no retry",

View File

@ -77,7 +77,8 @@ func (iw *largeItemWriter) Write(p []byte) (int, error) {
http.MethodPut, http.MethodPut,
iw.url, iw.url,
bytes.NewReader(p), bytes.NewReader(p),
headers) headers,
false)
if err != nil { if err != nil {
return 0, clues.Wrap(err, "uploading item").With( return 0, clues.Wrap(err, "uploading item").With(
"upload_id", iw.parentID, "upload_id", iw.parentID,

View File

@ -23,13 +23,19 @@ import (
// GockClient produces a new exchange api client that can be // GockClient produces a new exchange api client that can be
// mocked using gock. // mocked using gock.
func gockClient(creds account.M365Config, counter *count.Bus) (Client, error) { func gockClient(
s, err := graph.NewGockService(creds, counter) creds account.M365Config,
counter *count.Bus,
opts ...graph.Option,
) (Client, error) {
s, err := graph.NewGockService(creds, counter, opts...)
if err != nil { if err != nil {
return Client{}, err return Client{}, err
} }
li, err := graph.NewGockService(creds, counter, graph.NoTimeout()) opts = append(opts, graph.NoTimeout())
li, err := graph.NewGockService(creds, counter, opts...)
if err != nil { if err != nil {
return Client{}, err return Client{}, err
} }

View File

@ -55,7 +55,8 @@ func makeAC(
cli, err := api.NewClient( cli, err := api.NewClient(
creds, creds,
control.DefaultOptions(), control.DefaultOptions(),
count.New()) count.New(),
opts...)
if err != nil { if err != nil {
return api.Client{}, clues.WrapWC(ctx, err, "constructing api client") return api.Client{}, clues.WrapWC(ctx, err, "constructing api client")
} }

View File

@ -76,20 +76,11 @@ func (suite *siteIntegrationSuite) TestSites_GetByID() {
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
sites, err := suite.cli.Sites(ctx, fault.New(true)) site, err := suite.cli.SiteByID(ctx, suite.m365.Site.ID)
assert.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
assert.NotEmpty(t, sites) assert.NotEmpty(t, site.WebURL)
assert.NotEmpty(t, site.ID)
for _, s := range sites { assert.NotEmpty(t, site.OwnerType)
suite.Run("site_"+s.ID, func() {
t := suite.T()
site, err := suite.cli.SiteByID(ctx, s.ID)
require.NoError(t, err, clues.ToCore(err))
assert.NotEmpty(t, site.WebURL)
assert.NotEmpty(t, site.ID)
assert.NotEmpty(t, site.OwnerType)
})
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -21,7 +21,8 @@ application to connect to your *M365 tenant* and transfer data during backup and
## Corso concepts {#corso-concepts} ## Corso concepts {#corso-concepts}
* **Repository** refers to the storage location where Corso securely and efficiently stores encrypted *backups* of your * **Repository** refers to the storage location where Corso securely and efficiently stores encrypted *backups* of your
*M365 Services* data. See [Repositories](../repos) for more information. *M365 Service*'s data. See [Repositories](../repos) for more information.
* **Backup** is a copy of your *M365 Services* data to be used for restores in case of deletion, loss, or corruption of the * **Backup** is a copy of a resource of your *M365 Service*'s data to be used for restores in case of deletion, loss,
original data. Corso performs backups incrementally, and each backup only captures data that has changed between backup iterations. or corruption of the original data. Corso performs backups incrementally, and each backup only captures data that has
changed between backup iterations.

View File

@ -79,12 +79,23 @@ const config = {
srcDark: 'img/corso_horizontal_logo_white.svg', srcDark: 'img/corso_horizontal_logo_white.svg',
}, },
items: [ items: [
{
type: 'doc',
docId: 'quickstart',
position: 'left',
label: 'Quick Start',
},
{ {
type: 'doc', type: 'doc',
docId: 'intro', docId: 'intro',
position: 'left', position: 'left',
label: 'Docs', label: 'Docs',
}, },
{
href: 'https://discord.gg/63DTTSnuhT',
label: 'Community',
position: 'left',
},
{ {
to: '/blog', to: '/blog',
label: 'Blog', label: 'Blog',
@ -106,30 +117,12 @@ const config = {
}, },
links: [ links: [
{ {
title: 'Resources', title: 'Open Source',
items: [ items: [
{ {
label: 'Docs', label: 'Docs',
to: '/docs/intro', to: '/docs/intro',
}, },
],
},
{
title: 'Community',
items: [
{
label: 'Discord',
href: 'https://discord.gg/63DTTSnuhT',
},
{
label: 'Twitter',
href: 'https://twitter.com/CorsoBackup',
},
],
},
{
title: 'More',
items: [
{ {
label: 'Blog', label: 'Blog',
to: '/blog', to: '/blog',
@ -138,6 +131,26 @@ const config = {
label: 'GitHub', label: 'GitHub',
href: 'https://github.com/alcionai/corso', href: 'https://github.com/alcionai/corso',
}, },
{
label: 'Corso Discord',
href: 'https://discord.gg/63DTTSnuhT',
},
],
},
{
title: ' ',
},
{
title: 'Alcion, Powered by Corso',
items: [
{
label: 'Backup as a Service',
href: 'https://www.alcion.ai',
},
{
label: 'Alcion Discord',
href: 'https://www.alcion.ai/discord',
},
], ],
}, },
], ],

1811
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "3.1.1", "@docusaurus/core": "3.1.1",
"@docusaurus/plugin-google-gtag": "^3.1.1", "@docusaurus/plugin-google-gtag": "^3.5.1",
"@docusaurus/preset-classic": "3.1.1", "@docusaurus/preset-classic": "3.1.1",
"@loadable/component": "^5.16.3", "@loadable/component": "^5.16.3",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
@ -26,17 +26,17 @@
"feather-icons": "^4.29.1", "feather-icons": "^4.29.1",
"jarallax": "^2.2.0", "jarallax": "^2.2.0",
"mdx-mermaid": "^2.0.0", "mdx-mermaid": "^2.0.0",
"mermaid": "^10.8.0", "mermaid": "^10.9.0",
"prism-react-renderer": "^2.1.0", "prism-react-renderer": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.3.0",
"sass": "^1.70.0", "sass": "^1.79.1",
"tiny-slider": "^2.9.4", "tiny-slider": "^2.9.4",
"tw-elements": "1.0.0-alpha13", "tw-elements": "1.0.0-alpha13",
"wow.js": "^1.2.2" "wow.js": "^1.2.2"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "3.1.1", "@docusaurus/module-type-aliases": "3.5.1",
"@iconify/react": "^4.1.1", "@iconify/react": "^4.1.1",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"postcss": "^8.4.33", "postcss": "^8.4.33",

View File

@ -33,7 +33,7 @@ export default function CTA() {
<br /> Microsoft 365 Data! <br /> Microsoft 365 Data!
</h3> </h3>
<h6 className="text-white/50 text-lg font-semibold"> <h6 className="text-white/50 text-lg font-semibold">
Corso is Free and Open Source Corso (Free and Open Source) or <br/> Alcion (Managed Backup as a Service)
</h6> </h6>
</div> </div>
</div> </div>
@ -41,15 +41,24 @@ export default function CTA() {
<div className="mt-8"> <div className="mt-8">
<div className="section-title text-md-start"> <div className="section-title text-md-start">
<p className="text-white/50 max-w-xl mx-auto mb-2"> <p className="text-white/50 max-w-xl mx-auto mb-2">
Follow our quick-start guide to start protecting your Whether you want to self-host or use a managed service, we have you covered!
business-critical Microsoft 365 data in just a few
minutes.
</p> </p>
<a
href="https://www.alcion.ai/"
className="!text-white !no-underline flex flex-row items-center !hover:text-white"
>
Try Alcion{" "}
<Icon
icon="uim:angle-right-b"
className="align-middle"
/>
</a>
<p></p>
<a <a
href="docs/quickstart/" href="docs/quickstart/"
className="!text-white !no-underline flex flex-row items-center !hover:text-white" className="!text-white !no-underline flex flex-row items-center !hover:text-white"
> >
Get Started{" "} Corso Quickstart{" "}
<Icon <Icon
icon="uim:angle-right-b" icon="uim:angle-right-b"
className="align-middle" className="align-middle"

View File

@ -34,10 +34,17 @@ export default function Hero() {
<div className="mt-12 !z-10 mb-6 flex flex-col 2xs:flex-row items-center justify-center 2xs:space-y-0 space-y-4 2xs:space-x-4"> <div className="mt-12 !z-10 mb-6 flex flex-col 2xs:flex-row items-center justify-center 2xs:space-y-0 space-y-4 2xs:space-x-4">
<a <a
href="../docs/quickstart/" href="https://github.com/alcionai/corso/releases" target="_blank"
className="text-2xl !z-10 !no-underline hover:text-white py-2 px-6 font-bold btn bg-indigo-800 hover:bg-indigo-900 border-indigo-800 hover:border-indigo-900 text-white rounded-md" className="text-2xl !z-10 !no-underline hover:text-white py-2 px-6 font-bold btn bg-indigo-800 hover:bg-indigo-900 border-indigo-800 hover:border-indigo-900 text-white rounded-md"
> >
Quick Start Download
</a>
<a
href="https://www.alcion.ai/"
className="text-2xl !z-10 !no-underline hover:text-white py-2 px-6 font-bold btn bg-indigo-200 hover:bg-indigo-400 border-indigo-600 hover:border-indigo-800 text-blue rounded-md"
>
Try Alcion (Corso SaaS)
</a> </a>
</div> </div>

View File

@ -213,9 +213,9 @@ export default function KeyLoveFAQ() {
Community Community
</h3> </h3>
<p className="text-slate-400"> <p className="text-slate-400">
The Corso community provides a venue for M365 admins to share and The Corso community provides a venue for Microsoft 365 admins to share and
learn about the importance of data protection as well as best learn about the importance of data protection as well as best
practices around M365 secure configuration and compliance practices around Microsoft 365 secure configuration and compliance
management. management.
</p> </p>
<ul className="list-none text-slate-400 mt-4"> <ul className="list-none text-slate-400 mt-4">
@ -279,8 +279,7 @@ export default function KeyLoveFAQ() {
</h3> </h3>
<p className="text-slate-400"> <p className="text-slate-400">
Corso provides secure data backup that protects customers against Corso provides secure data backup that protects customers against
accidental data loss, service provider downtime, and malicious accidental data loss and service provider downtime.
threats including ransomware attacks.
</p> </p>
<ul className="list-none text-slate-400 mt-4"> <ul className="list-none text-slate-400 mt-4">
<li className="mb-1 flex"> <li className="mb-1 flex">
@ -331,7 +330,7 @@ export default function KeyLoveFAQ() {
Robust Backups Robust Backups
</h3> </h3>
<p className="text-slate-400"> <p className="text-slate-400">
Corso, purpose-built for M365 protection, provides easy-to-use Corso, purpose-built for Microsoft 365 protection, provides easy-to-use
comprehensive backup and restore workflows that reduces backup comprehensive backup and restore workflows that reduces backup
time, improve time-to-recovery, reduce admin overhead, and replace time, improve time-to-recovery, reduce admin overhead, and replace
unreliable scripts or workarounds. unreliable scripts or workarounds.
@ -342,7 +341,7 @@ export default function KeyLoveFAQ() {
className="text-indigo-600 text-xl mr-2" className="text-indigo-600 text-xl mr-2"
icon="material-symbols:check-circle-outline" icon="material-symbols:check-circle-outline"
/>{" "} />{" "}
Constantly updated M365 Graph Data engine Constantly updated Microsoft 365 Graph Data engine
</li> </li>
<li className="mb-1 flex"> <li className="mb-1 flex">
<Icon <Icon
@ -462,7 +461,7 @@ export default function KeyLoveFAQ() {
<div className="md:col-span-6"> <div className="md:col-span-6">
<div className="accordion space-y-3" id="accordionExample"> <div className="accordion space-y-3" id="accordionExample">
<div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden"> <div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden">
<h2 <h2
className="accordion-header mb-0 !cursor-pointer font-semibold" className="accordion-header mb-0 !cursor-pointer font-semibold"
id="headingOne" id="headingOne"
@ -475,7 +474,7 @@ export default function KeyLoveFAQ() {
aria-expanded="false" aria-expanded="false"
aria-controls="collapseOne" aria-controls="collapseOne"
> >
<span>What platforms does Corso run on?</span> <span>How do I choose between Corso and Alcion, powered by Corso?</span>
</button> </button>
</h2> </h2>
<div <div
@ -486,8 +485,7 @@ export default function KeyLoveFAQ() {
> >
<div className="accordion-body p-5"> <div className="accordion-body p-5">
<p className="text-slate-400 !visible dark:text-gray-400"> <p className="text-slate-400 !visible dark:text-gray-400">
Corso has both native binaries and container images for Corso is a good fit for basic backup while Alcion is a better fit if you need increased reliability, security, and support.
Windows, Linux, and macOS.
</p> </p>
</div> </div>
</div> </div>
@ -505,9 +503,7 @@ export default function KeyLoveFAQ() {
aria-expanded="false" aria-expanded="false"
aria-controls="collapse2" aria-controls="collapse2"
> >
<span> <span>What platforms does Corso run on?</span>
What Microsoft 365 services can I backup using Corso?
</span>
</button> </button>
</h2> </h2>
<div <div
@ -518,8 +514,8 @@ export default function KeyLoveFAQ() {
> >
<div className="accordion-body p-5"> <div className="accordion-body p-5">
<p className="text-slate-400 !visible dark:text-gray-400"> <p className="text-slate-400 !visible dark:text-gray-400">
Corso currently supports OneDrive, Exchange, SharePoint, Corso has both native binaries and container images for
and Teams. Windows, Linux, and macOS.
</p> </p>
</div> </div>
</div> </div>
@ -537,7 +533,9 @@ export default function KeyLoveFAQ() {
aria-expanded="false" aria-expanded="false"
aria-controls="collapse3" aria-controls="collapse3"
> >
<span>What object storage does Corso support?</span> <span>
What Microsoft 365 services can I backup using Corso?
</span>
</button> </button>
</h2> </h2>
<div <div
@ -545,6 +543,36 @@ export default function KeyLoveFAQ() {
className="accordion-collapse collapse" className="accordion-collapse collapse"
aria-labelledby="heading3" aria-labelledby="heading3"
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
>
<div className="accordion-body p-5">
<p className="text-slate-400 !visible dark:text-gray-400">
Corso currently supports OneDrive, Exchange, SharePoint,
and Teams.
</p>
</div>
</div>
</div>
<div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden">
<h2
className="accordion-header mb-0 !cursor-pointer font-semibold"
id="heading4"
>
<button
className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse4"
aria-expanded="false"
aria-controls="collapse4"
>
<span>What object storage does Corso support?</span>
</button>
</h2>
<div
id="collapse4"
className="accordion-collapse collapse"
aria-labelledby="heading4"
data-bs-parent="#accordionExample"
> >
<div className="accordion-body p-5"> <div className="accordion-body p-5">
<p className="text-slate-400 dark:text-gray-400 !visible"> <p className="text-slate-400 dark:text-gray-400 !visible">
@ -559,23 +587,23 @@ export default function KeyLoveFAQ() {
<div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden"> <div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden">
<h2 <h2
className="accordion-header mb-0 font-semibold" className="accordion-header mb-0 font-semibold"
id="heading4" id="heading5"
> >
<button <button
className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left" className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left"
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#collapse4" data-bs-target="#collapse5"
aria-expanded="false" aria-expanded="false"
aria-controls="collapse4" aria-controls="collapse5"
> >
<span>How can I get help for Corso?</span> <span>How can I get help for Corso?</span>
</button> </button>
</h2> </h2>
<div <div
id="collapse4" id="collapse5"
className="accordion-collapse collapse" className="accordion-collapse collapse"
aria-labelledby="heading4" aria-labelledby="heading5"
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
> >
<div className="accordion-body p-5"> <div className="accordion-body p-5">
@ -605,23 +633,23 @@ export default function KeyLoveFAQ() {
<div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden"> <div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden">
<h2 <h2
className="accordion-header mb-0 !cursor-pointer font-semibold" className="accordion-header mb-0 !cursor-pointer font-semibold"
id="heading5" id="heading6"
> >
<button <button
className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left" className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left"
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#collapse5" data-bs-target="#collapse6"
aria-expanded="false" aria-expanded="false"
aria-controls="collapse5" aria-controls="collapse6"
> >
<span>What is Corso's open-source license?</span> <span>What is Corso's open-source license?</span>
</button> </button>
</h2> </h2>
<div <div
id="collapse5" id="collapse6"
className="accordion-collapse collapse" className="accordion-collapse collapse"
aria-labelledby="heading5" aria-labelledby="heading6"
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
> >
<div className="accordion-body p-5"> <div className="accordion-body p-5">
@ -635,23 +663,23 @@ export default function KeyLoveFAQ() {
<div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden"> <div className="accordion-item !text-white relative shadow dark:shadow-gray-800 rounded-md overflow-hidden">
<h2 <h2
className="accordion-header mb-0 !cursor-pointer font-semibold" className="accordion-header mb-0 !cursor-pointer font-semibold"
id="heading6" id="heading7"
> >
<button <button
className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left" className="transition accordion-button-custom text-white !text-base !cursor-pointer border-none outline-none collapsed focus:outline-none !bg-transparent flex justify-between items-center p-5 w-full font-bold text-left"
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#collapse6" data-bs-target="#collapse7"
aria-expanded="false" aria-expanded="false"
aria-controls="collapse6" aria-controls="collapse7"
> >
<span>How do I request a new feature?</span> <span>How do I request a new feature?</span>
</button> </button>
</h2> </h2>
<div <div
id="collapse6" id="collapse7"
className="accordion-collapse collapse" className="accordion-collapse collapse"
aria-labelledby="heading6" aria-labelledby="heading7"
data-bs-parent="#accordionExample" data-bs-parent="#accordionExample"
> >
<div className="accordion-body p-5"> <div className="accordion-body p-5">

96
website/static/img/corso_horizontal_logo.svg Executable file → Normal file

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 23 KiB

96
website/static/img/corso_horizontal_logo_white.svg Executable file → Normal file
View File

@ -1 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 523"><defs><style>.cls-1{fill:#fff;}</style></defs><g><path class="cls-1" d="M134.51,129.94c28.63,0,54.6,7.95,75.81,22.79,11.67,7.95,14.31,23.33,6.36,36.58-7.42,12.19-25.98,12.73-37.64,5.83-12.73-7.42-28.63-12.19-44.53-12.19-41.35,0-77.93,30.22-77.93,76.34s36.58,75.81,77.93,75.81c15.91,0,31.81-4.77,44.53-12.19,11.66-6.89,30.22-6.36,37.64,5.83,7.95,13.25,5.3,28.63-6.36,36.58-21.21,14.84-47.18,22.8-75.81,22.8C63.47,388.12,2.5,337.76,2.5,259.29S63.47,129.94,134.51,129.94Z"/><path class="cls-1" d="M261.22,258.23c0-78.46,58.85-128.3,128.83-128.3s129.88,49.83,129.88,128.3-59.37,129.89-129.88,129.89-128.83-51.43-128.83-129.89Zm204.64,0c0-45.59-34.46-75.28-75.81-75.28s-74.75,29.69-74.75,75.28,33.93,76.87,74.75,76.87,75.81-30.22,75.81-76.87Z"/><path class="cls-1" d="M633.91,293.75v64.15c0,14.84-12.19,27.57-28.1,27.57-14.84,0-26.51-12.72-26.51-27.57V160.15c0-14.84,11.67-27.57,26.51-27.57,15.91,0,28.1,12.72,28.1,27.57v31.81c12.73-44,37.11-62.03,67.86-62.03,7.95,0,15.91,.53,23.33,2.12,13.79,3.18,22.8,16.97,19.62,31.28-4.77,23.86-28.63,18.03-44.53,18.03-46.65,0-66.27,46.65-66.27,112.39Z"/><path class="cls-1" d="M788.19,302.24c13.25-5.3,23.33,1.59,27.57,10.6,10.08,19.09,29.16,29.69,53.55,29.69s42.94-11.13,42.94-29.69c0-15.9-15.38-22.79-33.4-27.03l-33.4-7.95c-52.48-14.32-71.57-42.94-68.39-82.7,3.18-36.58,42.94-65.21,88.53-65.21,32.87,0,63.09,10.6,79.53,36.58,7.42,12.72,3.71,25.44-4.77,31.81-9.01,7.42-20.15,6.89-31.81-3.18-13.78-12.19-29.69-16.97-42.41-16.97-13.79,0-29.16,4.77-34.46,13.25-4.24,6.89-4.77,13.78-2.12,21.21,3.18,9.54,18.02,14.31,31.28,18.02l38.17,9.54c53.54,13.25,64.68,48.24,64.68,73.16,0,47.71-41.88,74.75-98.61,74.75-38.17,0-76.87-20.15-90.13-56.2-4.24-13.25,1.59-25.44,13.25-29.68Z"/><path class="cls-1" d="M1006.61,258.23c0-78.46,58.85-128.3,128.83-128.3s129.88,49.83,129.88,128.3-59.37,129.89-129.88,129.89-128.83-51.43-128.83-129.89Zm204.64,0c0-45.59-34.46-75.28-75.81-75.28s-74.75,29.69-74.75,75.28,33.93,76.87,74.75,76.87,75.81-30.22,75.81-76.87Z"/></g><path class="cls-1" d="M1658.37,520.7c-33.98,0-65.93-13.23-89.96-37.26l-221.94-221.93,221.94-221.93c24.03-24.03,55.98-37.27,89.96-37.27s65.93,13.23,89.96,37.27l131.98,131.97c49.6,49.61,49.6,130.31,0,179.92l-131.98,131.98c-24.03,24.03-55.98,37.26-89.96,37.26Zm-264.47-259.2l198.22,198.22c36.53,36.53,95.97,36.52,132.5,0l131.98-131.98c36.53-36.53,36.53-95.97,0-132.5l-131.98-131.97c-17.7-17.7-41.22-27.44-66.25-27.44s-48.55,9.75-66.25,27.44l-198.22,198.22Z"/><g><path class="cls-1" d="M1813.99,260.82l-6.63-41.44c-1.01-7.17-6.37-11.15-14.81-11.15l-49.44-.14c-7.95,0-13.8-3.97-14.81-11.15l-.17-1.18c-1.61-11.39-9.61-20.86-20.58-24.34l-66.42-24.32c-1.66-.53-3.19-1.38-4.51-2.5-.48-.41-.8-.96-.97-1.57l-12.42-42.67c-.13-.43-.36-.83-.7-1.12-3.65-3-9.25-1.95-11.5,2.32l-24.5,54.88c-.89,1.68-1.4,3.54-1.5,5.45l3.01,25.01c-.14,2.64-1.08,5.2-2.7,7.3l-48.86,48.59c-5.2,5.2-16.08,16.08-16.08,16.08l136.84,136.85,12.2-50.1c1.05-5.87,5.91-10.29,11.84-10.77l25.89-2.09c80.88-3.46,81.72-26.8,104.9-63.08,1.35-2.12,2.07-4.58,2.07-7.08,0-.6-.04-1.21-.13-1.81Zm-106.45-40.35c-5.96,1.5-22.58,.54-24.08-5.43-1.5-5.95,12.71-14.66,18.66-16.15,5.96-1.5,12,2.12,13.5,8.08,1.49,5.95-2.13,12-8.08,13.49Z"/><path class="cls-1" d="M1680.04,153.81l-13.04-48.97c-.14-.53-.47-1.01-.92-1.33-2.89-2.07-7.06-1.18-8.79,2.09l-16.37,33.9,39.12,14.32Z"/><path class="cls-1" d="M1655.16,404.17l-.85,3.47c-1.93,7.9-11.75,10.65-17.49,4.9l-123.3-123.3-11.74-11.74,13.35-13.35,11.74,11.74,128.28,128.28Z"/></g></svg> <?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1920 632.51" style="enable-background:new 0 0 1920 632.51;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_00000065045999731516100160000007329899648576828572_);fill:#FFFFFF;}
.st1{fill:#FFFFFF;}
</style>
<g id="Layer_1">
<g>
<g>
<defs>
<rect id="SVGID_1_" y="2.64" width="1920" height="523"/>
</defs>
<clipPath id="SVGID_00000147923114548510084520000017867003880147110077_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M134.51,132.57
c28.63,0,54.6,7.95,75.81,22.79c11.66,7.95,14.31,23.33,6.36,36.58c-7.42,12.19-25.98,12.73-37.64,5.83
c-12.73-7.42-28.63-12.19-44.53-12.19c-41.35,0-77.93,30.22-77.93,76.34c0,46.12,36.58,75.81,77.93,75.81
c15.91,0,31.81-4.77,44.53-12.19c11.66-6.89,30.22-6.36,37.64,5.83c7.95,13.26,5.3,28.63-6.36,36.58
c-21.21,14.85-47.19,22.8-75.81,22.8C63.47,390.76,2.5,340.39,2.5,261.93C2.5,183.47,63.47,132.57,134.51,132.57"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M261.22,260.87
c0-78.46,58.85-128.29,128.83-128.29c70.51,0,129.89,49.83,129.89,128.29s-59.37,129.89-129.89,129.89
C320.06,390.76,261.22,339.33,261.22,260.87 M465.86,260.87c0-45.59-34.46-75.28-75.81-75.28c-40.82,0-74.75,29.69-74.75,75.28
c0,46.66,33.93,76.87,74.75,76.87C431.4,337.74,465.86,307.52,465.86,260.87"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M633.91,296.39v64.15
c0,14.85-12.19,27.57-28.1,27.57c-14.84,0-26.51-12.72-26.51-27.57V162.79c0-14.85,11.67-27.57,26.51-27.57
c15.91,0,28.1,12.72,28.1,27.57v31.81c12.73-44,37.11-62.02,67.86-62.02c7.95,0,15.91,0.53,23.33,2.12
c13.79,3.18,22.8,16.97,19.62,31.28c-4.77,23.86-28.63,18.03-44.53,18.03C653.53,184,633.91,230.65,633.91,296.39"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M788.19,304.87
c13.25-5.3,23.33,1.59,27.57,10.6c10.08,19.09,29.16,29.69,53.55,29.69c24.92,0,42.94-11.13,42.94-29.69
c0-15.9-15.38-22.79-33.4-27.03l-33.4-7.95c-52.48-14.32-71.57-42.94-68.39-82.71c3.18-36.58,42.94-65.21,88.53-65.21
c32.87,0,63.09,10.6,79.53,36.58c7.42,12.72,3.71,25.44-4.77,31.81c-9.01,7.42-20.15,6.89-31.81-3.18
c-13.78-12.19-29.69-16.97-42.41-16.97c-13.79,0-29.16,4.77-34.46,13.25c-4.24,6.89-4.77,13.78-2.12,21.21
c3.18,9.54,18.03,14.31,31.28,18.02l38.17,9.54c53.54,13.25,64.68,48.24,64.68,73.16c0,47.71-41.88,74.75-98.61,74.75
c-38.17,0-76.87-20.15-90.13-56.2C770.69,321.31,776.53,309.12,788.19,304.87"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M1006.61,260.87
c0-78.46,58.85-128.29,128.83-128.29c70.51,0,129.89,49.83,129.89,128.29s-59.37,129.89-129.89,129.89
C1065.46,390.76,1006.61,339.33,1006.61,260.87 M1211.25,260.87c0-45.59-34.46-75.28-75.81-75.28
c-40.82,0-74.75,29.69-74.75,75.28c0,46.66,33.93,76.87,74.75,76.87C1176.79,337.74,1211.25,307.52,1211.25,260.87"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M1658.37,523.34
c-33.98,0-65.93-13.24-89.96-37.26l-221.94-221.93l221.94-221.93c24.03-24.03,55.98-37.27,89.96-37.27
c33.98,0,65.93,13.24,89.96,37.27l131.98,131.97c49.6,49.61,49.6,130.31,0,179.92l-131.98,131.98
C1724.3,510.1,1692.35,523.34,1658.37,523.34 M1393.9,264.14l198.22,198.22c36.54,36.53,95.97,36.52,132.5,0l131.98-131.98
c36.53-36.53,36.53-95.96,0-132.5L1724.61,65.92c-17.7-17.7-41.22-27.44-66.25-27.44c-25.03,0-48.55,9.75-66.25,27.44
L1393.9,264.14z"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M1813.99,263.46
l-6.63-41.44c-1.01-7.18-6.37-11.15-14.81-11.15l-49.44-0.14c-7.95,0-13.8-3.97-14.81-11.15l-0.17-1.18
c-1.61-11.39-9.6-20.86-20.58-24.34l-66.42-24.32c-1.66-0.53-3.19-1.38-4.51-2.5c-0.48-0.4-0.8-0.96-0.97-1.57l-12.42-42.67
c-0.12-0.43-0.36-0.83-0.7-1.12c-3.65-3-9.24-1.95-11.5,2.32l-24.5,54.88c-0.89,1.68-1.4,3.55-1.5,5.45l3.01,25.01
c-0.14,2.64-1.08,5.2-2.7,7.3l-48.86,48.59c-5.2,5.2-16.08,16.08-16.08,16.08l136.84,136.85l12.2-50.1
c1.05-5.87,5.91-10.29,11.84-10.77l25.89-2.09c80.88-3.46,81.72-26.8,104.89-63.08c1.35-2.12,2.07-4.58,2.07-7.08
C1814.12,264.67,1814.07,264.06,1813.99,263.46 M1707.54,223.11c-5.96,1.5-22.58,0.54-24.08-5.43
c-1.5-5.95,12.71-14.66,18.66-16.15c5.96-1.5,12,2.12,13.5,8.09C1717.11,215.57,1713.49,221.62,1707.54,223.11"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M1680.04,156.45
L1667,107.48c-0.14-0.53-0.47-1.01-0.92-1.33c-2.89-2.07-7.06-1.18-8.79,2.09l-16.37,33.9L1680.04,156.45z"/>
<path style="clip-path:url(#SVGID_00000147923114548510084520000017867003880147110077_);fill:#FFFFFF;" d="M1655.16,406.81
l-0.85,3.47c-1.93,7.89-11.75,10.65-17.49,4.9l-123.3-123.3l-11.74-11.74l13.35-13.35l11.74,11.74L1655.16,406.81z"/>
</g>
<g>
<path class="st1" d="M523.8,510.47c5.57-9.64,17.49-14.55,30.17-14.55c24.41,0,44.78,17.77,44.78,46.11
c0,27.78-20.76,45.93-44.97,45.93c-12.11,0-24.41-5.29-29.98-14.74v3.97c0,5.29-4.42,9.83-10.19,9.83
c-5.38,0-9.61-4.54-9.61-9.83v-118.5c0-5.29,4.23-9.83,9.61-9.83c5.77,0,10.19,4.54,10.19,9.83V510.47z M551.48,569.06
c14.99,0,27.1-11.15,27.1-27.4s-12.11-26.84-27.1-26.84c-13.45,0-27.48,9.45-27.48,26.84
C523.99,558.85,536.87,569.06,551.48,569.06z"/>
<path class="st1" d="M645.84,623.3c-2.11,4.91-7.11,7.56-12.3,6.24s-8.07-7.37-6.15-12.28l14.61-35.34l-30.56-72.38
c-2.11-4.91,0.96-10.96,6.15-12.29c5.19-1.32,10.19,1.32,12.3,6.24l22.68,54.81l22.87-54.81c2.11-4.91,7.11-7.56,12.3-6.24
c5.19,1.32,8.26,7.37,6.15,12.29L645.84,623.3z"/>
<path class="st1" d="M828.41,573.4c-5.96,9.64-19.03,14.55-30.17,14.55c-24.22,0-45.55-17.95-45.55-46.11
s21.33-45.93,45.55-45.93c10.76,0,24.02,4.35,30.17,14.36v-3.59c0-5.29,4.23-9.83,9.61-9.83c5.77,0,10.19,4.54,10.19,9.83v70.5
c0,5.29-4.42,9.83-10.19,9.83c-5.38,0-9.61-4.54-9.61-9.83V573.4z M800.55,569.06c14.61,0,27.67-10.02,27.67-27.4
s-14.22-26.84-27.67-26.84c-14.99,0-27.48,10.58-27.48,26.84S785.56,569.06,800.55,569.06z"/>
<path class="st1" d="M894.91,577.18c0,5.29-4.42,9.83-10.19,9.83c-5.38,0-9.61-4.54-9.61-9.83v-118.5c0-5.29,4.23-9.83,9.61-9.83
c5.77,0,10.19,4.54,10.19,9.83V577.18z"/>
<path class="st1" d="M964.67,495.91c10.38,0,19.8,2.83,27.48,8.13c4.23,2.83,5.19,8.32,2.31,13.04
c-2.69,4.35-9.42,4.54-13.65,2.08c-4.61-2.65-10.38-4.35-16.14-4.35c-14.99,0-28.25,10.77-28.25,27.21s13.26,27.03,28.25,27.03
c5.77,0,11.53-1.7,16.14-4.35c4.23-2.46,10.96-2.27,13.65,2.08c2.88,4.72,1.92,10.21-2.31,13.04c-7.69,5.29-17.1,8.13-27.48,8.13
c-25.75,0-47.85-17.95-47.85-45.93C916.82,514.06,938.92,495.91,964.67,495.91z"/>
<path class="st1" d="M1026.55,449.8c7.3,0,13.07,5.29,13.07,12.28c0,6.99-5.77,12.29-13.07,12.29c-7.11,0-13.26-5.29-13.26-12.29
C1013.29,455.09,1019.44,449.8,1026.55,449.8z M1036.55,506.69c0-5.29-4.42-9.83-10.19-9.83c-5.38,0-9.61,4.54-9.61,9.83v70.5
c0,5.29,4.23,9.83,9.61,9.83c5.77,0,10.19-4.54,10.19-9.83V506.69z"/>
<path class="st1" d="M1058.07,541.65c0-27.97,21.33-45.74,46.7-45.74c25.56,0,47.08,17.77,47.08,45.74
c0,27.97-21.52,46.3-47.08,46.3C1079.4,587.95,1058.07,569.62,1058.07,541.65z M1132.25,541.65c0-16.25-12.49-26.84-27.48-26.84
c-14.8,0-27.1,10.58-27.1,26.84c0,16.63,12.3,27.4,27.1,27.4C1119.76,569.06,1132.25,558.28,1132.25,541.65z"/>
<path class="st1" d="M1173.38,506.69c0-5.29,4.42-9.83,10.19-9.83c5.38,0,9.61,4.54,9.61,9.83v4.35
c5.19-10.21,17.49-15.12,27.48-15.12c21.72,0,34.21,13.8,34.21,38.74v42.52c0,5.29-4.42,9.83-10.19,9.83
c-5.38,0-9.61-4.54-9.61-9.83v-40.26c0-13.99-7.3-21.92-18.83-21.92c-11.72,0-23.06,6.24-23.06,23.62v38.55
c0,5.29-4.42,9.83-10.19,9.83c-5.38,0-9.61-4.54-9.61-9.83V506.69z"/>
</g>
</g>
</g>
<g id="Layer_2">
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB