nmdc-runtime 2.9.0__py3-none-any.whl → 2.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nmdc-runtime might be problematic. Click here for more details.

Files changed (131) hide show
  1. nmdc_runtime/Dockerfile +167 -0
  2. nmdc_runtime/api/analytics.py +90 -0
  3. nmdc_runtime/api/boot/capabilities.py +9 -0
  4. nmdc_runtime/api/boot/object_types.py +126 -0
  5. nmdc_runtime/api/boot/triggers.py +84 -0
  6. nmdc_runtime/api/boot/workflows.py +116 -0
  7. nmdc_runtime/api/core/auth.py +208 -0
  8. nmdc_runtime/api/core/idgen.py +200 -0
  9. nmdc_runtime/api/core/metadata.py +788 -0
  10. nmdc_runtime/api/core/util.py +109 -0
  11. nmdc_runtime/api/db/mongo.py +435 -0
  12. nmdc_runtime/api/db/s3.py +37 -0
  13. nmdc_runtime/api/endpoints/capabilities.py +25 -0
  14. nmdc_runtime/api/endpoints/find.py +634 -0
  15. nmdc_runtime/api/endpoints/jobs.py +143 -0
  16. nmdc_runtime/api/endpoints/lib/helpers.py +274 -0
  17. nmdc_runtime/api/endpoints/lib/linked_instances.py +180 -0
  18. nmdc_runtime/api/endpoints/lib/path_segments.py +165 -0
  19. nmdc_runtime/api/endpoints/metadata.py +260 -0
  20. nmdc_runtime/api/endpoints/nmdcschema.py +502 -0
  21. nmdc_runtime/api/endpoints/object_types.py +38 -0
  22. nmdc_runtime/api/endpoints/objects.py +270 -0
  23. nmdc_runtime/api/endpoints/operations.py +78 -0
  24. nmdc_runtime/api/endpoints/queries.py +701 -0
  25. nmdc_runtime/api/endpoints/runs.py +98 -0
  26. nmdc_runtime/api/endpoints/search.py +38 -0
  27. nmdc_runtime/api/endpoints/sites.py +205 -0
  28. nmdc_runtime/api/endpoints/triggers.py +25 -0
  29. nmdc_runtime/api/endpoints/users.py +214 -0
  30. nmdc_runtime/api/endpoints/util.py +796 -0
  31. nmdc_runtime/api/endpoints/workflows.py +353 -0
  32. nmdc_runtime/api/entrypoint.sh +7 -0
  33. nmdc_runtime/api/main.py +425 -0
  34. nmdc_runtime/api/middleware.py +43 -0
  35. nmdc_runtime/api/models/capability.py +14 -0
  36. nmdc_runtime/api/models/id.py +92 -0
  37. nmdc_runtime/api/models/job.py +37 -0
  38. nmdc_runtime/api/models/lib/helpers.py +78 -0
  39. nmdc_runtime/api/models/metadata.py +11 -0
  40. nmdc_runtime/api/models/nmdc_schema.py +146 -0
  41. nmdc_runtime/api/models/object.py +180 -0
  42. nmdc_runtime/api/models/object_type.py +20 -0
  43. nmdc_runtime/api/models/operation.py +66 -0
  44. nmdc_runtime/api/models/query.py +246 -0
  45. nmdc_runtime/api/models/query_continuation.py +111 -0
  46. nmdc_runtime/api/models/run.py +161 -0
  47. nmdc_runtime/api/models/site.py +87 -0
  48. nmdc_runtime/api/models/trigger.py +13 -0
  49. nmdc_runtime/api/models/user.py +140 -0
  50. nmdc_runtime/api/models/util.py +260 -0
  51. nmdc_runtime/api/models/workflow.py +15 -0
  52. nmdc_runtime/api/openapi.py +178 -0
  53. nmdc_runtime/api/swagger_ui/assets/custom-elements.js +522 -0
  54. nmdc_runtime/api/swagger_ui/assets/script.js +247 -0
  55. nmdc_runtime/api/swagger_ui/assets/style.css +155 -0
  56. nmdc_runtime/api/swagger_ui/swagger_ui.py +34 -0
  57. nmdc_runtime/config.py +7 -8
  58. nmdc_runtime/minter/adapters/repository.py +22 -2
  59. nmdc_runtime/minter/config.py +2 -0
  60. nmdc_runtime/minter/domain/model.py +55 -1
  61. nmdc_runtime/minter/entrypoints/fastapi_app.py +1 -1
  62. nmdc_runtime/mongo_util.py +1 -2
  63. nmdc_runtime/site/backup/nmdcdb_mongodump.py +1 -1
  64. nmdc_runtime/site/backup/nmdcdb_mongoexport.py +1 -3
  65. nmdc_runtime/site/changesheets/data/OmicsProcessing-to-catted-Biosamples.tsv +1561 -0
  66. nmdc_runtime/site/changesheets/scripts/missing_neon_soils_ecosystem_data.py +311 -0
  67. nmdc_runtime/site/changesheets/scripts/neon_soils_add_ncbi_ids.py +210 -0
  68. nmdc_runtime/site/dagster.yaml +53 -0
  69. nmdc_runtime/site/entrypoint-daemon.sh +26 -0
  70. nmdc_runtime/site/entrypoint-dagit-readonly.sh +26 -0
  71. nmdc_runtime/site/entrypoint-dagit.sh +26 -0
  72. nmdc_runtime/site/export/ncbi_xml.py +633 -13
  73. nmdc_runtime/site/export/ncbi_xml_utils.py +115 -1
  74. nmdc_runtime/site/graphs.py +8 -22
  75. nmdc_runtime/site/ops.py +147 -181
  76. nmdc_runtime/site/repository.py +2 -112
  77. nmdc_runtime/site/resources.py +16 -3
  78. nmdc_runtime/site/translation/gold_translator.py +4 -12
  79. nmdc_runtime/site/translation/neon_benthic_translator.py +0 -1
  80. nmdc_runtime/site/translation/neon_soil_translator.py +4 -5
  81. nmdc_runtime/site/translation/neon_surface_water_translator.py +0 -2
  82. nmdc_runtime/site/translation/submission_portal_translator.py +84 -68
  83. nmdc_runtime/site/translation/translator.py +63 -1
  84. nmdc_runtime/site/util.py +8 -3
  85. nmdc_runtime/site/validation/util.py +10 -5
  86. nmdc_runtime/site/workspace.yaml +13 -0
  87. nmdc_runtime/static/NMDC_logo.svg +1073 -0
  88. nmdc_runtime/static/ORCID-iD_icon_vector.svg +4 -0
  89. nmdc_runtime/static/README.md +5 -0
  90. nmdc_runtime/static/favicon.ico +0 -0
  91. nmdc_runtime/util.py +90 -48
  92. nmdc_runtime-2.11.0.dist-info/METADATA +46 -0
  93. nmdc_runtime-2.11.0.dist-info/RECORD +128 -0
  94. {nmdc_runtime-2.9.0.dist-info → nmdc_runtime-2.11.0.dist-info}/WHEEL +1 -2
  95. nmdc_runtime/containers.py +0 -14
  96. nmdc_runtime/core/db/Database.py +0 -15
  97. nmdc_runtime/core/exceptions/__init__.py +0 -23
  98. nmdc_runtime/core/exceptions/base.py +0 -47
  99. nmdc_runtime/core/exceptions/token.py +0 -13
  100. nmdc_runtime/domain/users/queriesInterface.py +0 -18
  101. nmdc_runtime/domain/users/userSchema.py +0 -37
  102. nmdc_runtime/domain/users/userService.py +0 -14
  103. nmdc_runtime/infrastructure/database/db.py +0 -3
  104. nmdc_runtime/infrastructure/database/models/user.py +0 -10
  105. nmdc_runtime/lib/__init__.py +0 -1
  106. nmdc_runtime/lib/extract_nmdc_data.py +0 -41
  107. nmdc_runtime/lib/load_nmdc_data.py +0 -121
  108. nmdc_runtime/lib/nmdc_dataframes.py +0 -829
  109. nmdc_runtime/lib/nmdc_etl_class.py +0 -402
  110. nmdc_runtime/lib/transform_nmdc_data.py +0 -1117
  111. nmdc_runtime/site/drsobjects/ingest.py +0 -93
  112. nmdc_runtime/site/drsobjects/registration.py +0 -131
  113. nmdc_runtime/site/translation/emsl.py +0 -43
  114. nmdc_runtime/site/translation/gold.py +0 -53
  115. nmdc_runtime/site/translation/jgi.py +0 -32
  116. nmdc_runtime/site/translation/util.py +0 -132
  117. nmdc_runtime/site/validation/jgi.py +0 -43
  118. nmdc_runtime-2.9.0.dist-info/METADATA +0 -214
  119. nmdc_runtime-2.9.0.dist-info/RECORD +0 -84
  120. nmdc_runtime-2.9.0.dist-info/top_level.txt +0 -1
  121. /nmdc_runtime/{client → api}/__init__.py +0 -0
  122. /nmdc_runtime/{core → api/boot}/__init__.py +0 -0
  123. /nmdc_runtime/{core/db → api/core}/__init__.py +0 -0
  124. /nmdc_runtime/{domain → api/db}/__init__.py +0 -0
  125. /nmdc_runtime/{domain/users → api/endpoints}/__init__.py +0 -0
  126. /nmdc_runtime/{infrastructure → api/endpoints/lib}/__init__.py +0 -0
  127. /nmdc_runtime/{infrastructure/database → api/models}/__init__.py +0 -0
  128. /nmdc_runtime/{infrastructure/database/models → api/models/lib}/__init__.py +0 -0
  129. /nmdc_runtime/{site/drsobjects/__init__.py → api/models/minter.py} +0 -0
  130. {nmdc_runtime-2.9.0.dist-info → nmdc_runtime-2.11.0.dist-info}/entry_points.txt +0 -0
  131. {nmdc_runtime-2.9.0.dist-info → nmdc_runtime-2.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,522 @@
1
+ /**
2
+ * This JavaScript file contains the implementations of various Web Components
3
+ * (which get defined as "custom elements") designed for use with Swagger UI.
4
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements
5
+ *
6
+ * Developer notes:
7
+ *
8
+ * 1. Because we define our CSS and HTML within `String.raw` tagged templates [1],
9
+ * the popular "lit-html" extension [2] (if installed) for VS Code will apply
10
+ * CSS and HTML syntax highlighting to those strings.
11
+ *
12
+ * References:
13
+ * - [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
14
+ * - [2] https://marketplace.visualstudio.com/items?itemName=bierner.lit-html
15
+ *
16
+ *****************************************************************************/
17
+
18
+ // If FastAPI's Swagger UI JavaScript hasn't been executed yet, throw an error explaining the situation.
19
+ // Note: FastAPI runs the Swagger UI JavaScript in a way that creates a global variable named `ui`.
20
+ // Note: We do a `typeof` check because, if we did `ui === undefined` and `ui` weren't defined,
21
+ // JavaScript would raise its own `ReferenceError` exception with its own message.
22
+ if (typeof ui === "undefined") {
23
+ throw new Error("FastAPI's Swagger UI JavaScript has not been executed yet.");
24
+ }
25
+
26
+ /**
27
+ * Endpoint search widget, implemented as a Web Component.
28
+ *
29
+ * The widget provides a search input field, and a results panel that lists
30
+ * the API endpoints whose URL paths match the search term. When the user
31
+ * clicks on a search result, the browser navigates to the corresponding
32
+ * endpoint in the Swagger UI page (via a full page refresh/"deep link").
33
+ *
34
+ * References:
35
+ * - https://developer.mozilla.org/en-US/docs/Web/API/Web_components
36
+ * - https://open-wc.org/codelabs/basics/web-components#2
37
+ */
38
+ class EndpointSearchWidget extends HTMLElement {
39
+ constructor() {
40
+ super();
41
+
42
+ // Initialize all instance properties.
43
+ this.inputEl = null;
44
+ this.resultsPanelEl = null;
45
+ this.noEndpointsMessageEl = null;
46
+ this.resultsListEl = null;
47
+ this.endpoints = [];
48
+
49
+ // Redefine each callback method so that, within that method, the "this" variable
50
+ // refers to this instance of the class.
51
+ //
52
+ // Note: If we didn't do this, the value of the "this" variable would depend upon how
53
+ // the method was invoked (e.g. if invoked via an event listener, the "this"
54
+ // variable would refer to the element that triggered the event; e.g. a text field).
55
+ //
56
+ // Reference:
57
+ // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#creating_a_bound_function
58
+ //
59
+ this._handleInput = this._handleInput.bind(this);
60
+ }
61
+
62
+ /**
63
+ * Initializes the custom HTML element after it's been added to the DOM.
64
+ *
65
+ * Note: The `connectedCallback` function gets called automatically when
66
+ * this HTML element gets added to the DOM.
67
+ *
68
+ * References:
69
+ * - https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
70
+ */
71
+ connectedCallback() {
72
+ // Create a Shadow DOM tree specific to this custom HTML element, so that its styles don't
73
+ // impact other elements on the page and so styles on the page don't impact this element. [1]
74
+ //
75
+ // Note: This will populate the instance's inherited `this.shadowRoot` property [2]
76
+ // with a reference to the root node of this Shadow DOM tree. [3]
77
+ //
78
+ // References:
79
+ // - [1] https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#creating_a_shadow_dom
80
+ // - [2] https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot
81
+ // - [3] https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
82
+ //
83
+ this.attachShadow({ mode: "open" });
84
+
85
+ // Implement the structure and style of the widget.
86
+ //
87
+ // Note: We chose the specific style rules shown below in an attempt to
88
+ // match the "look and feel" of the Swagger UI page. At least some
89
+ // of the seemingly-arbitrarily-chosen values were copied verbatim
90
+ // from native Swagger UI elements (via a web browser's DevTools).
91
+ //
92
+ this.shadowRoot.innerHTML = String.raw`
93
+ <style>
94
+ :host {
95
+ display: block;
96
+ margin: 0 auto;
97
+ max-width: 1460px;
98
+ font-family: sans-serif;
99
+ font-size: 14px;
100
+ color: #3b4151;
101
+ }
102
+ .container {
103
+ margin: 20px 20px 0px 20px;
104
+ }
105
+ input {
106
+ padding: 8px 10px;
107
+ box-sizing: border-box;
108
+ border: 1px solid #d9d9d9;
109
+ border-radius: 4px;
110
+ width: 100%;
111
+ }
112
+ .results-panel {
113
+ background-color: rgba(0, 0, 0, .05);
114
+ border-radius: 4px;
115
+ }
116
+ .results-panel p.no-endpoints {
117
+ font-size: 14px;
118
+ padding: 26px;
119
+ }
120
+ ul {
121
+ list-style-type: none;
122
+ padding-left: 26px;
123
+ padding-right: 26px;
124
+ font-family: monospace;
125
+ }
126
+ ul > li:first-child {
127
+ padding-top: 26px;
128
+ }
129
+ ul > li:last-child {
130
+ padding-bottom: 26px;
131
+ }
132
+ li > a {
133
+ color: #4990e2;
134
+ text-decoration: none;
135
+ }
136
+ li > a:hover {
137
+ color: #1f69c0;
138
+ }
139
+ li > a .http-method {
140
+ display: inline-block;
141
+ min-width: 52px;
142
+ margin-right: 10px;
143
+ }
144
+ .matching-substring {
145
+ background-color: #f9f871;
146
+ }
147
+ .hidden {
148
+ /*
149
+ Note: When we hide elements via 'display: none', we do not have to also set 'aria-hidden="true"'.
150
+ MDN says: 'aria-hidden="true" should not be added when [...] The element [...] is hidden with 'display: none'.
151
+ Reference: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden
152
+ */
153
+ display: none;
154
+ }
155
+ </style>
156
+
157
+ <div class="container">
158
+ <input name="search-term" placeholder="Find an endpoint..." aria-label="Search term for finding an endpoint" />
159
+ <div class="results-panel">
160
+ <p class="no-endpoints hidden">No matching endpoints.</p>
161
+ <ul class="results-list"></ul>
162
+ </div>
163
+ </div>
164
+ `;
165
+ this.inputEl = this.shadowRoot.querySelector("input");
166
+ this.resultsPanelEl = this.shadowRoot.querySelector(".results-panel");
167
+ this.noEndpointsMessageEl = this.shadowRoot.querySelector(".no-endpoints");
168
+ this.resultsListEl = this.shadowRoot.querySelector(".results-list");
169
+
170
+ // Make an array of all the endpoints that Swagger UI knows about. This will be our search index.
171
+ //
172
+ // Note: In an earlier implementation of this step, we queried the DOM for this information.
173
+ // However, that didn't work when any of the endpoint groups were collapsed. So, instead,
174
+ // we access the data structure returned by the `SwaggerUI()` constructor, which is
175
+ // invoked by the JavaScript code built into FastAPI (that JavaScript code assigns
176
+ // the return value to a global variable named `ui`, which we access here).
177
+ //
178
+ // Note: Should we eventually choose to display endpoint's description also, we can be obtain it
179
+ // via the endpoint's operation Map; i.e. `opMap.get("operation").get("description")`.
180
+ //
181
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
182
+ //
183
+ const operationMaps = Array.from(ui.specSelectors.operations());
184
+ this.endpoints = operationMaps.map(opMap => ({
185
+ urlPath: opMap.get("path"), // e.g. "/studies/{study_id}"
186
+ httpMethod: opMap.get("method").toUpperCase(), // e.g. "GET"
187
+ operationId: opMap.get("operation").get("operationId"), // e.g. "find_studies_studies_get"
188
+ tag: opMap.get("operation").get("tags").get(0), // e.g. "Metadata access: Find"
189
+ }));
190
+ console.debug(`Found ${this.endpoints.length} endpoints in OpenAPI schema`);
191
+
192
+ // Make it so the search results list gets updated whenever the user changes the search input.
193
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event
194
+ this.inputEl.addEventListener("input", this._handleInput);
195
+ }
196
+
197
+ /**
198
+ * Callback function that handles the "input" event on the endpoint search input field.
199
+ * The main role of this method is to extract the search term from the event object
200
+ * and pass it to the method that updates the search results to reflect that search term.
201
+ *
202
+ * @param {Event} event The "input" event.
203
+ */
204
+ _handleInput(event) {
205
+ this.updateSearchResults(event.target.value);
206
+ }
207
+
208
+ /**
209
+ * Callback function designed to be passed in as the `compareFn` argument to `Array.prototype.sort()`.
210
+ * This callback function that sorts endpoint objects by comparing the URL paths and, if those are
211
+ * equal, comparing the HTTP methods.
212
+ *
213
+ * @param {{ urlPath: string, httpMethod: string }} a
214
+ * @param {{ urlPath: string, httpMethod: string }} b
215
+ * @returns {number} A negative number if `a` comes before `b`,
216
+ * a positive number if `a` comes after `b`,
217
+ * and 0 if `a` and `b` are equivalent.
218
+ *
219
+ * References:
220
+ * - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#comparefn
221
+ * - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
222
+ */
223
+ _sortEndpoints(a, b) {
224
+ if (a.urlPath.localeCompare(b.urlPath) === 0) {
225
+ return a.httpMethod.localeCompare(b.httpMethod);
226
+ } else {
227
+ return a.urlPath.localeCompare(b.urlPath);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Wraps the part of the full string that matches the test string (case insensitively),
233
+ * in a `<span>` element having the class "matching-substring".
234
+ *
235
+ * Note: That will allow us to style that part of the string differently from the other parts.
236
+ *
237
+ * @param {string} fullStr The full string.
238
+ * @param {string} searchTermStr The string to search for within the full string.
239
+ * @returns {HTMLSpanElement} A `<span>` element containing the full string, with the matching
240
+ * substring—if any—wrapped in a `<span class="matching-substring">`.
241
+ */
242
+ _wrapMatchingSubstring(fullStr, searchTermStr) {
243
+ // Create the outer `<span>` element.
244
+ const fullStrSpanEl = document.createElement("span");
245
+
246
+ // Return early if we know the search term string _cannot_ exist within the full string.
247
+ if (fullStr.length === 0 || searchTermStr.length === 0 || searchTermStr.length > fullStr.length) {
248
+ fullStrSpanEl.textContent = fullStr;
249
+ return fullStrSpanEl;
250
+ }
251
+
252
+ // Lowercase both strings to enable case-insensitive comparison.
253
+ const fullStrLowercase = fullStr.toLowerCase();
254
+ const searchTermStrLowercase = searchTermStr.toLowerCase();
255
+
256
+ // Locate the (lowercased) substring within the (lowercased) full string.
257
+ const substrCharIdx = fullStrLowercase.indexOf(searchTermStrLowercase);
258
+
259
+ // Return early if we know the search term string _doesn't_ exist within the full string.
260
+ if (substrCharIdx === -1) {
261
+ fullStrSpanEl.textContent = fullStr;
262
+ return fullStrSpanEl;
263
+ }
264
+
265
+ // Split the full string into three parts.
266
+ const preSubstrChars = fullStr.substring(0, substrCharIdx);
267
+ const substringChars = fullStr.substring(substrCharIdx, substrCharIdx + searchTermStr.length)
268
+ const postSubstrChars = fullStr.substring(substrCharIdx + searchTermStr.length);
269
+
270
+ // Create and populate the inner `<span>` element containing the matching substring.
271
+ const matchingSubstrSpanEl = document.createElement("span");
272
+ matchingSubstrSpanEl.classList.add("matching-substring");
273
+ matchingSubstrSpanEl.textContent = substringChars;
274
+
275
+ // Populate the outer `<span>` element.
276
+ fullStrSpanEl.replaceChildren(preSubstrChars, matchingSubstrSpanEl, postSubstrChars);
277
+ return fullStrSpanEl;
278
+ }
279
+
280
+ /**
281
+ * Returns a Swagger UI-compliant "Deep Link" URL pointing to the specific API endpoint
282
+ * on a Swagger UI page.
283
+ *
284
+ * @param {string} tagNameStr Name of the tag of the endpoint being linked to.
285
+ * @param {string} operationIdStr Operation ID of the endpoint being linked to.
286
+ * @returns {string} A Swagger UI-compliant "Deep Link" URL that points to the
287
+ * specified API endpoint on a Swagger UI page.
288
+ *
289
+ * Reference: https://swagger.io/docs/open-source-tools/swagger-ui/usage/deep-linking/#usage
290
+ */
291
+ _buildSwaggerUIDeepLinkUrl(tagNameStr, operationIdStr) {
292
+ const urlWithoutQueryStr = window.location.origin + window.location.pathname;
293
+ const encodedTagNameStr = encodeURIComponent(tagNameStr);
294
+ const encodedOperationIdPart = encodeURIComponent(operationIdStr);
295
+ return `${urlWithoutQueryStr}/#${encodedTagNameStr}/${encodedOperationIdPart}`;
296
+ }
297
+
298
+ /**
299
+ * Updates the search results list so it shows the endpoints whose URL paths contain the search term.
300
+ *
301
+ * @param {string} searchTerm The search term.
302
+ */
303
+ updateSearchResults(searchTerm) {
304
+ // Special case: If the search term is empty, clear the search results.
305
+ if (searchTerm.trim().length === 0) {
306
+ this.resultsListEl.replaceChildren();
307
+ this.noEndpointsMessageEl.classList.add("hidden");
308
+ return;
309
+ }
310
+
311
+ // Lowercase the search term to enable case-insensitive comparison.
312
+ const lowercaseSearchTerm = searchTerm.toLowerCase();
313
+
314
+ // Make a list of all endpoints whose URL paths contains the search term.
315
+ const matchingEndpoints = this.endpoints.filter(item => {
316
+ const lowercaseUrlPath = item.urlPath.toLowerCase();
317
+ return lowercaseUrlPath.includes(lowercaseSearchTerm);
318
+ });
319
+
320
+ // If there are no matching endpoints, clear the search results.
321
+ if (matchingEndpoints.length === 0) {
322
+ this.resultsListEl.replaceChildren();
323
+ this.noEndpointsMessageEl.classList.remove("hidden");
324
+ return;
325
+ } else {
326
+ this.noEndpointsMessageEl.classList.add("hidden");
327
+ }
328
+
329
+ // Make an array of hyperlinks to the matching endpoints.
330
+ const resultEls = matchingEndpoints.sort(this._sortEndpoints).map(matchingEndpoint => {
331
+ // Wrap the part of the URL path that matches the search term, so that we can
332
+ // style it differently from the rest of the URL path.
333
+ const urlPathSpanEl = this._wrapMatchingSubstring(matchingEndpoint.urlPath, lowercaseSearchTerm);
334
+
335
+ // Build a hyperlink that points to the matching endpoint.
336
+ //
337
+ // Note: The reason we don't use something like `scrollTo` or `scrollIntoView`
338
+ // is that the target element may not be mounted to the DOM right now,
339
+ // due to it being within a collapsed section. Instead, we send the
340
+ // browser to the "deep link" URL (Swagger UI will automatically expand
341
+ // the relevant section when the page loads that "deep link" URL).
342
+ //
343
+ const aEl = document.createElement("a");
344
+ const httpMethodSpanEl = document.createElement("span");
345
+ httpMethodSpanEl.classList.add("http-method"); // e.g. "<span class="http-method">GET</span>"
346
+ httpMethodSpanEl.textContent = matchingEndpoint.httpMethod;
347
+ aEl.href = this._buildSwaggerUIDeepLinkUrl(matchingEndpoint.tag, matchingEndpoint.operationId);
348
+ aEl.replaceChildren(httpMethodSpanEl, urlPathSpanEl);
349
+
350
+ // Return a list item consisting of the hyperlink.
351
+ const liEl = document.createElement("li");
352
+ liEl.replaceChildren(aEl);
353
+ return liEl;
354
+ });
355
+ this.resultsListEl.replaceChildren(...resultEls);
356
+ }
357
+
358
+ /**
359
+ * Callback function that will be automatically invoked when the element is removed from the DOM.
360
+ *
361
+ * References:
362
+ * - https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
363
+ */
364
+ disconnectedCallback() {
365
+ console.debug("Cleaning up event listener(s)");
366
+ if (this.inputEl !== null) {
367
+ this.inputEl.removeEventListener("input", this._handleInput);
368
+ }
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Ellipses button with tooltip, implemented as a Web Component.
374
+ *
375
+ * The tooltip has a slot [1], which can be used to specify the tooltip's text.
376
+ *
377
+ * References:
378
+ * - [1] https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/slot
379
+ */
380
+ class EllipsesButton extends HTMLElement {
381
+ // List the names of HTML attributes this element will respond to.
382
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes
383
+ static observedAttributes = ["is-open"];
384
+
385
+ constructor() {
386
+ super();
387
+
388
+ this.tooltipWrapperEl = null;
389
+
390
+ // Ensure that, within our callback methods, the "this" variable refers to this class instance.
391
+ this.stopEventPropagation = this.stopEventPropagation.bind(this);
392
+ }
393
+
394
+ connectedCallback() {
395
+ this.attachShadow({ mode: "open" });
396
+ this.shadowRoot.innerHTML = String.raw`
397
+ <style>
398
+ .container {
399
+ display: inline-flex;
400
+ }
401
+ button {
402
+ cursor: pointer;
403
+ background-color: transparent;
404
+ border: none;
405
+ line-height: 0px;
406
+ border-radius: 9999px; /* circle */
407
+
408
+ transition: background-color 0.2s ease-in-out,
409
+ color 0.2s ease-in-out;
410
+ }
411
+ button:hover {
412
+ color: #1f69c0;
413
+ background-color: #f4f4f4;
414
+ }
415
+
416
+ .tooltip-wrapper {
417
+ cursor: auto;
418
+ display: inline-flex;
419
+ align-items: center;
420
+ opacity: 0;
421
+ transition: opacity 0.2s ease-in-out;
422
+ }
423
+ button:hover + .tooltip-wrapper {
424
+ opacity: 1;
425
+ }
426
+ .tooltip-arrow {
427
+ margin-right: -4px;
428
+ fill: #f4f4f4;
429
+ }
430
+ .tooltip-box {
431
+ font-family: sans-serif;
432
+ background: #f4f4f4;
433
+ color: #333;
434
+ padding: 4px 8px;
435
+ border-radius: 4px;
436
+ font-size: 11px;
437
+ }
438
+ .hidden {
439
+ display: none;
440
+ }
441
+ </style>
442
+
443
+ <span class="container">
444
+ <button aria-describedby="tooltip" name="toggle-button">
445
+ <!-- Ellipses (row of three dots) -->
446
+ <svg width="12" height="12" viewBox="0 0 12 12" class="ellipses">
447
+ <ellipse cx="2" cy="6" rx="1" ry="1" fill="currentColor" />
448
+ <ellipse cx="6" cy="6" rx="1" ry="1" fill="currentColor" />
449
+ <ellipse cx="10" cy="6" rx="1" ry="1" fill="currentColor" />
450
+ </svg>
451
+
452
+ <!-- X (the symbol for closing) -->
453
+ <svg width="12" height="12" viewBox="0 0 12 12" class="cross hidden">
454
+ <line x1="1" y1="1" x2="11" y2="11" stroke="currentColor" stroke-width="1"/>
455
+ <line x1="11" y1="1" x2="1" y2="11" stroke="currentColor" stroke-width="1"/>
456
+ </svg>
457
+ </button>
458
+
459
+ <!-- Tooltip (rectangle with left-pointing arrowhead) -->
460
+ <div class="tooltip-wrapper">
461
+ <svg class="tooltip-arrow" width="16" height="24" viewBox="0 0 16 24">
462
+ <polygon points="0,12 16,6 16,18" />
463
+ </svg>
464
+ <div class="tooltip-box">
465
+ <slot role="tooltip">...</slot>
466
+ </div>
467
+ </div>
468
+ </span>
469
+ `;
470
+
471
+ // Prevent click events on the tooltip from bubbling up to higher-level HTML elements.
472
+ this.tooltipWrapperEl = this.shadowRoot.querySelector(".tooltip-wrapper");
473
+ this.tooltipWrapperEl.addEventListener("click", this.stopEventPropagation);
474
+ }
475
+
476
+ /**
477
+ * Prevents the specified event from bubbling up to higher-level HTML elements.
478
+ *
479
+ * @param {Event} event The event.
480
+ */
481
+ stopEventPropagation(event) {
482
+ event.stopPropagation();
483
+ }
484
+
485
+ /**
486
+ * Handle a change in the value (or the addition, removal, or replacement) of any HTML attribute
487
+ * whose name is listed in the `observedAttributes` list.
488
+ *
489
+ * @param {string} attributeName Name of the attribute whose value changed
490
+ * @param {string | null} oldValue Value the attribute changed _from_
491
+ * @param {string | null} newValue Value the attribute changed _to_
492
+ *
493
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes
494
+ */
495
+ attributeChangedCallback(attributeName, oldValue, newValue) {
496
+ // If the "is-open" attribute changed and its value is "true", show the "X" (Castlevania boomerang) icon.
497
+ // Otherwise, show the "..." (Pac-Man bait) icon.
498
+ if (attributeName === "is-open") {
499
+ const ellipsesEl = this.shadowRoot.querySelector("button .ellipses");
500
+ const crossEl = this.shadowRoot.querySelector("button .cross");
501
+ if (typeof newValue === "string" && newValue.toLowerCase() === "true") {
502
+ ellipsesEl.classList.add("hidden");
503
+ crossEl.classList.remove("hidden");
504
+ } else {
505
+ crossEl.classList.add("hidden");
506
+ ellipsesEl.classList.remove("hidden");
507
+ }
508
+ }
509
+ }
510
+
511
+ disconnectedCallback() {
512
+ console.debug("Cleaning up event listener(s)");
513
+ if (this.tooltipWrapperEl !== null) {
514
+ this.tooltipWrapperEl.removeEventListener("click", this.stopEventPropagation);
515
+ }
516
+ }
517
+ }
518
+
519
+ // Register custom HTML elements (i.e. `<endpoint-search-widget>` and `<ellipses-button>`).
520
+ // Docs: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#registering_a_custom_element
521
+ customElements.define("endpoint-search-widget", EndpointSearchWidget);
522
+ customElements.define("ellipses-button", EllipsesButton);