From 5ac5597ca235ef746e5bfdffd47fa4a931306003 Mon Sep 17 00:00:00 2001 From: Stig Ofstad Date: Tue, 9 Apr 2024 12:15:58 +0200 Subject: [PATCH 1/2] feat: add product endpoint --- api/src/app.py | 15 +++- api/src/calculators/fraction_interpolator.py | 83 ++++++++----------- api/src/config.py | 2 + api/src/controllers/products.py | 31 ++++++- api/src/util/authentication.py | 2 + api/src/util/azure_table.py | 2 +- docker-compose.yaml | 1 + .../Bridging/Graphs/BridgeGraph.tsx | 2 - .../Components/Bridging/InputContainer.tsx | 2 +- .../Combinations/CombinationCard.tsx | 2 +- web/src/Utils.ts | 2 +- 11 files changed, 83 insertions(+), 61 deletions(-) diff --git a/api/src/app.py b/api/src/app.py index 6f79114..a3b251e 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -7,7 +7,7 @@ from controllers.combination import bridge_from_combination from controllers.optimal_bridge import bridgeRequestHandler from controllers.optimizer import optimizer_request_handler -from controllers.products import products_get +from controllers.products import products_get, products_post from controllers.report import create_report from util.authentication import authorize from util.sync_share_point_az import sync_all @@ -22,10 +22,17 @@ def init_api(): app = init_api() -@app.route("/api/products", methods=["GET"]) +@app.route("/api/products", methods=["GET", "POST"]) @authorize -def products(): - return products_get() +def products() -> Response: + if request.method == "GET": + return products_get() + if request.method == "POST": + name: str = request.json.get("productName") + supplier: str = request.json.get("productSupplier") + product_data: [[float, float]] = request.json.get("productData") + return products_post(name, supplier, product_data) + raise ValueError("Invalid method for endpoint") @app.route("/api/report", methods=["POST"]) diff --git a/api/src/calculators/fraction_interpolator.py b/api/src/calculators/fraction_interpolator.py index 7642fff..eeeaa08 100644 --- a/api/src/calculators/fraction_interpolator.py +++ b/api/src/calculators/fraction_interpolator.py @@ -1,63 +1,48 @@ -import csv -from copy import copy +from bisect import bisect_left from numpy import log from calculators.bridge import SIZE_STEPS -def lookup_smaller(table: dict, value: float): - n = [i for i in table.keys() if i <= value] - return max(n) +def find_closest_bigger_index(array: list[float], target: float) -> int: + index = bisect_left(array, target) + if index == 0: + raise ValueError("Failed to find closest biggest index") + return index + 1 -def lookup_bigger(table: dict, value: float): - n = [i for i in table.keys() if i >= value] - return min(n) +def log_interpolate_or_extrapolate(xMin: float, yMin: float, xMax: float, yMax: float, z: float) -> float: + increase = (log(z) - log(xMin)) / (log(xMax) - log(xMin)) + return increase * (yMax - yMin) + yMin -def fraction_interpolator(x: list[float], y: list[float], z: list[float]) -> list[float]: - table_dict = dict(zip(x, y, strict=False)) - max_x = max(x) +def fraction_interpolator_and_extrapolator( + xArray: list[float], yArray: list[float], zArray: list[float] = SIZE_STEPS +) -> list[float]: + sizes_dict = {size: 0 for size in zArray} # Populate size_dict with 0 values + starting_index = find_closest_bigger_index(zArray, min(xArray)) - 1 + s = sum(yArray) + print(f"Sum Y: {s}") - z_table = {} - for _z in z: - if _z > max_x: - break - smaller_x = lookup_smaller(table_dict, _z) - bigger_x = lookup_bigger(table_dict, _z) - z_table[_z] = {"x1": smaller_x, "x2": bigger_x, "y1": table_dict[smaller_x], "y2": table_dict[bigger_x]} + for zIndex, z in enumerate(zArray[starting_index:]): + if z < xArray[0]: # Don't extrapolate down from first measuring point + continue + # If z is above the range of xArray, use the last two points for extrapolation + elif z > xArray[-1]: + yz = log_interpolate_or_extrapolate(xArray[-2], yArray[-2], xArray[-1], yArray[-1], z) + else: + # Find the interval that z falls into for interpolation + for i in range(1, len(xArray)): + if xArray[i - 1] <= z <= xArray[i]: + yz = log_interpolate_or_extrapolate(xArray[i - 1], yArray[i - 1], xArray[i], yArray[i], z) + break - for zz, values in z_table.items(): - x1 = values["x1"] - x2 = values["x2"] - y1 = values["y1"] - y2 = values["y2"] + if yz > 100: # 100% volume has been reached. Stop extrapolation. Set all remaining to 100% + for key in zArray[zIndex + starting_index :]: + sizes_dict[key] = 100 + return list(sizes_dict.values()) - values["j"] = (y2 - y1) / (log(x2) - log(x1)) * (log(zz) - log(x1)) + y1 - return [round(v["j"], 3) for v in z_table.values()] + sizes_dict[z] = round(yz, ndigits=3) - -def from_csv_to_csv(): - with open("test_data/interpolate_input.csv") as csvfile: - reader = csv.DictReader(csvfile) - fields_copy = copy(reader.fieldnames) - fields_copy.pop(0) - products = {name: [] for name in fields_copy} - a_x = [] - for line in reader: - a_x.append(float(line["Size"])) - for n in products: - products[n].append(float(line[n])) - - for name in products: - b_y = fraction_interpolator(x=a_x, y=products[name], z=SIZE_STEPS) - with open(f"test_data/{name}.csv", "w+") as newcsvfile: - writer = csv.DictWriter(newcsvfile, fieldnames=["Size", "Cumulative"]) - writer.writeheader() - for step, interpol_value in zip(SIZE_STEPS, b_y, strict=False): - writer.writerow({"Size": step, "Cumulative": interpol_value}) - - -if __name__ == "__main__": - from_csv_to_csv() + return list(sizes_dict.values()) diff --git a/api/src/config.py b/api/src/config.py index bc73125..6b7f605 100644 --- a/api/src/config.py +++ b/api/src/config.py @@ -4,6 +4,7 @@ class Config: + AUTH_DISABLED = os.getenv("AUTH_DISABLED", "false") TABLE_ACCOUNT_NAME = os.getenv("TABLE_ACCOUNT_NAME", "lcmdevstorage") TABLE_KEY = os.getenv("TABLE_KEY") BLOB_CONTAINER_NAME = "lcm-file-blobs" @@ -19,4 +20,5 @@ class Config: DEFAULT_MAX_ITERATIONS = 100 HOME_DIR = str(Path(__file__).parent.absolute()) PRODUCT_TABLE_NAME = "products" + CUSTOM_PRODUCT_TABLE = "interpolatedproducts" named_supplier = ("Baker Hughes", "Halliburton", "Schlumberger") diff --git a/api/src/controllers/products.py b/api/src/controllers/products.py index 743abed..b5ff6e4 100644 --- a/api/src/controllers/products.py +++ b/api/src/controllers/products.py @@ -1,8 +1,9 @@ from cachetools import TTLCache, cached from flask import Response +from calculators.fraction_interpolator import fraction_interpolator_and_extrapolator from config import Config -from util.azure_table import get_service +from util.azure_table import get_table_service, sanitize_row_key def sort_products(products: dict[str, dict]): @@ -14,7 +15,11 @@ def sort_products(products: dict[str, dict]): @cached(cache=TTLCache(maxsize=128, ttl=300)) def products_get(): - products = get_service().query_entities(Config.PRODUCT_TABLE_NAME) + table_service = get_table_service() + + share_point_products = table_service.query_entities(Config.PRODUCT_TABLE_NAME) + interpolated_products = table_service.query_entities(Config.CUSTOM_PRODUCT_TABLE) + products = [*share_point_products, *interpolated_products] products_response = {} for p in products: @@ -36,3 +41,25 @@ def products_get(): sorted_products = sort_products(products_response) return sorted_products + + +def products_post(product_name: str, supplier_name: str, product_data: [[float, float]]) -> Response: + product_id = sanitize_row_key(product_name) + sizes = [p[0] for p in product_data] + cumulative = [p[1] for p in product_data] + table_entry = { + "PartitionKey": Config.CUSTOM_PRODUCT_TABLE, + "RowKey": product_id, + "id": product_id, + "title": product_name, + "supplier": supplier_name, + "cumulative": str(fraction_interpolator_and_extrapolator(sizes, cumulative)), + "sack_size": 25, + "environment": "Green", + "cost": 100, + "co2": 1000, + } + + get_table_service().insert_entity(Config.CUSTOM_PRODUCT_TABLE, table_entry) + products_get.cache_clear() + return table_entry diff --git a/api/src/util/authentication.py b/api/src/util/authentication.py index 3c6d191..ddbdb1b 100644 --- a/api/src/util/authentication.py +++ b/api/src/util/authentication.py @@ -45,6 +45,8 @@ def decode_jwt(token): def authorize(f): @wraps(f) def wrap(*args, **kwargs): + if Config.AUTH_DISABLED == "true": + return f(*args, **kwargs) if "Authorization" not in request.headers: abort(401, "Missing 'Authorization' header") try: diff --git a/api/src/util/azure_table.py b/api/src/util/azure_table.py index a5e5709..fd6583f 100644 --- a/api/src/util/azure_table.py +++ b/api/src/util/azure_table.py @@ -47,7 +47,7 @@ def sanitize_row_key(value: str) -> str: return value.replace("/", "-").replace("\\", "-").replace("#", "").replace("?", "-").replace(" ", "").lower() -def get_service(): +def get_table_service() -> TableService: return TableService(account_name=Config.TABLE_ACCOUNT_NAME, account_key=Config.TABLE_KEY) diff --git a/docker-compose.yaml b/docker-compose.yaml index 2ce7ca4..c334c4d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,7 @@ services: restart: unless-stopped environment: ENVIRONMENT: development + AUTH_DISABLED: "true" FLASK_DEBUG: "true" TABLE_KEY: ${TABLE_KEY} ports: diff --git a/web/src/Components/Bridging/Graphs/BridgeGraph.tsx b/web/src/Components/Bridging/Graphs/BridgeGraph.tsx index f7efea9..d2547ee 100644 --- a/web/src/Components/Bridging/Graphs/BridgeGraph.tsx +++ b/web/src/Components/Bridging/Graphs/BridgeGraph.tsx @@ -15,8 +15,6 @@ type BridgeGraphProps = { const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { - console.log(payload) - console.log(label) return (
{`Particle size : ${label}µm`}
diff --git a/web/src/Components/Bridging/InputContainer.tsx b/web/src/Components/Bridging/InputContainer.tsx index dee2bb2..41a90c9 100644 --- a/web/src/Components/Bridging/InputContainer.tsx +++ b/web/src/Components/Bridging/InputContainer.tsx @@ -139,7 +139,7 @@ const InputContainer = ({ Optimal Bridge:
{[10, 50, 90].map(d => ( -

+

D{d}: {findDValue(optimalBridgeGraphData, d, 'Bridge')} {'\u00B5m'}

diff --git a/web/src/Components/Combinations/CombinationCard.tsx b/web/src/Components/Combinations/CombinationCard.tsx index 0ef043a..d684716 100644 --- a/web/src/Components/Combinations/CombinationCard.tsx +++ b/web/src/Components/Combinations/CombinationCard.tsx @@ -101,8 +101,8 @@ export const CombinationCard = ({ setD90(findDValue(graphData, 90, combination.name)) }) .catch(error => { - ErrorToast(`${error.response.data}`, error.response.status) console.error('fetch error' + error) + ErrorToast(`${error.response.data}`, error.response.status) }) }, [combination, allProducts]) diff --git a/web/src/Utils.ts b/web/src/Utils.ts index ea8f4c9..4464724 100644 --- a/web/src/Utils.ts +++ b/web/src/Utils.ts @@ -45,7 +45,7 @@ export function findDValue(graphData: GraphData[], goalYValue: number, bridgeNam } return false }) - if (!indexOfClosestHigherYValue) throw new Error('Failed to find D-value of bridge') + if (!indexOfClosestHigherYValue) return 0 // interpolate the values to get an approx value for the exact D requested return linearInterpolation( From 44cb37654552255ceded41e9360c4eb1b771064e Mon Sep 17 00:00:00 2001 From: Stig Ofstad Date: Tue, 9 Apr 2024 15:13:04 +0200 Subject: [PATCH 2/2] feat: create product UI --- api/src/calculators/fraction_interpolator.py | 4 - api/src/controllers/products.py | 8 + api/src/tests/test_interpolator.py | 109 +++++++++++++- web/package.json | 4 +- web/src/Api.ts | 4 + web/src/Components/Navbar/CreateProduct.tsx | 146 +++++++++++++++++++ web/src/Components/Navbar/Navbar.tsx | 4 +- web/src/Types.ts | 6 + web/yarn.lock | 124 ++++++++-------- 9 files changed, 336 insertions(+), 73 deletions(-) create mode 100644 web/src/Components/Navbar/CreateProduct.tsx diff --git a/api/src/calculators/fraction_interpolator.py b/api/src/calculators/fraction_interpolator.py index eeeaa08..7456b48 100644 --- a/api/src/calculators/fraction_interpolator.py +++ b/api/src/calculators/fraction_interpolator.py @@ -7,8 +7,6 @@ def find_closest_bigger_index(array: list[float], target: float) -> int: index = bisect_left(array, target) - if index == 0: - raise ValueError("Failed to find closest biggest index") return index + 1 @@ -22,8 +20,6 @@ def fraction_interpolator_and_extrapolator( ) -> list[float]: sizes_dict = {size: 0 for size in zArray} # Populate size_dict with 0 values starting_index = find_closest_bigger_index(zArray, min(xArray)) - 1 - s = sum(yArray) - print(f"Sum Y: {s}") for zIndex, z in enumerate(zArray[starting_index:]): if z < xArray[0]: # Don't extrapolate down from first measuring point diff --git a/api/src/controllers/products.py b/api/src/controllers/products.py index b5ff6e4..b551ccc 100644 --- a/api/src/controllers/products.py +++ b/api/src/controllers/products.py @@ -45,6 +45,14 @@ def products_get(): def products_post(product_name: str, supplier_name: str, product_data: [[float, float]]) -> Response: product_id = sanitize_row_key(product_name) + + for p in product_data: + if not len(p) == 2: + return Response("Invalid product data. Must be two valid numbers for each line", 400) + + if not isinstance(p[0], float | int) or not isinstance(p[1], float | int): + return Response("Invalid product data. Must be two valid numbers for each line", 400) + sizes = [p[0] for p in product_data] cumulative = [p[1] for p in product_data] table_entry = { diff --git a/api/src/tests/test_interpolator.py b/api/src/tests/test_interpolator.py index acd73e2..a6be060 100644 --- a/api/src/tests/test_interpolator.py +++ b/api/src/tests/test_interpolator.py @@ -1,6 +1,6 @@ import unittest -from calculators.fraction_interpolator import fraction_interpolator +from calculators.fraction_interpolator import fraction_interpolator_and_extrapolator class InterpolatorTest(unittest.TestCase): @@ -39,6 +39,107 @@ def test_interpolator(): 35.88136905, ] - b_x = [39.8, 60.2, 104.7] - b_y = fraction_interpolator(x=a_x, y=a_y, z=b_x) - assert b_y == [0.214, 1.464, 16.634] + b_y = fraction_interpolator_and_extrapolator(xArray=a_x, yArray=a_y) + assert b_y == [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.192, + 0.229, + 0.496, + 0.875, + 1.376, + 2.122, + 4.313, + 8.304, + 13.633, + 19.459, + 25.537, + 30.036, + 32.986, + 34.748, + 35.978, + 37.233, + 38.454, + 39.729, + 40.968, + 42.215, + 43.449, + 44.698, + 45.938, + 47.186, + 48.422, + 49.668, + 50.913, + 52.167, + 53.403, + 54.638, + 55.914, + 57.149, + 58.385, + 59.646, + 60.871, + 62.119, + 63.366, + ] diff --git a/web/package.json b/web/package.json index f4f5133..11b3913 100644 --- a/web/package.json +++ b/web/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { - "@equinor/eds-core-react": "^0.34.0", - "@equinor/eds-icons": "^0.19.3", + "@equinor/eds-core-react": "^0.36.1", + "@equinor/eds-icons": "^0.21.0", "@equinor/eds-tokens": "^0.9.2", "axios": "^1.6.2", "react": "^18.2.0", diff --git a/web/src/Api.ts b/web/src/Api.ts index 4944843..e432e75 100644 --- a/web/src/Api.ts +++ b/web/src/Api.ts @@ -1,4 +1,5 @@ import axios from 'axios' +import { TNewProduct } from './Types' const BASE_PATH = '/api' @@ -39,6 +40,9 @@ class ProductsApi { async getProductsApi(token: string) { return axios.get(`${BASE_PATH}/products`, { headers: { Authorization: `Bearer ${token}` } }) } + async postProductsApi(token: string, newProduct: TNewProduct) { + return axios.post(`${BASE_PATH}/products`, newProduct, { headers: { Authorization: `Bearer ${token}` } }) + } } class FractionsApi { diff --git a/web/src/Components/Navbar/CreateProduct.tsx b/web/src/Components/Navbar/CreateProduct.tsx new file mode 100644 index 0000000..e340dc0 --- /dev/null +++ b/web/src/Components/Navbar/CreateProduct.tsx @@ -0,0 +1,146 @@ +import React, { useContext, useState } from 'react' +// @ts-ignore +import { Button, Dialog, Icon, TextField, Table } from '@equinor/eds-core-react' + +import { ProductsAPI } from '../../Api' +import styled from 'styled-components' +import { ErrorToast } from '../Common/Toast' +import { AuthContext } from 'react-oauth2-code-pkce' +import { IAuthContext } from 'react-oauth2-code-pkce' +import { upload } from '@equinor/eds-icons' +import { TNewProduct } from '../../Types' + +const ButtonWrapper = styled.div` + display: flex; + justify-content: space-between; + width: 100%; +` +const parseCumulativeProductCurve = (curve: string): number[][] => { + // Split the input string into lines using the newline character + const lines = curve.split('\n') + + // Map each line into an array of two elements + const parsedData = lines.map(line => { + // Replace commas with periods to handle European-style decimals + const cleanLine = line.replace(/,/g, '.') + // Split each line by spaces or tabs to separate the numbers + const elements = cleanLine.split(/\s+|\t+/) + // Convert the string elements to numbers + return elements.map(element => parseFloat(element)) + }) + + return parsedData +} + +export const RefreshButton = () => { + const [dialogOpen, setDialogOpen] = useState(false) + const [loading, setLoading] = useState(false) + const { token }: IAuthContext = useContext(AuthContext) + const [newProduct, setNewProduct] = useState() + const [newProductData, setNewProductData] = useState([]) + + const postProduct = () => { + ProductsAPI.postProductsApi(token, { ...newProduct, productData: newProductData }) + .then(() => { + setLoading(false) + window.location.reload() + }) + .catch(error => { + ErrorToast(`${error.response.data}`, error.response.status) + console.error('fetch error' + error) + setLoading(false) + }) + } + + return ( + <> + + + + Define a new product + + +
+ setNewProduct({ ...newProduct, productName: event.target.value })} + /> + setNewProduct({ ...newProduct, productSupplier: event.target.value })} + /> +
+
+

+ Paste the product's measured data values here. Make sure it's been parsed correctly by inspecting the + table below before submitting. +

+

+ The format of the pasted data should be two numbers on each line (space or tab separated), where the first + number is the fraction size in micron of the measuring point, and the other the cumulative volume + percentage. +

+

+ The Optimizer requires each product to have 100 data points, from 0.01 - 3500 microns. If the data you + provide is missing data, the values will be interpolated and extrapolated. +

+ setNewProductData(parseCumulativeProductCurve(event.target.value))} + /> +
+
+ + + + Index + Fraction(micron) + Cumulative + + + {newProductData.map((dataPoint: any, index) => ( + + {index} + {dataPoint[0]} + {dataPoint[1]} + + ))} +
+
+
+ + + + + + +
+ + ) +} + +export default RefreshButton diff --git a/web/src/Components/Navbar/Navbar.tsx b/web/src/Components/Navbar/Navbar.tsx index 276861e..b1ea764 100644 --- a/web/src/Components/Navbar/Navbar.tsx +++ b/web/src/Components/Navbar/Navbar.tsx @@ -3,6 +3,7 @@ import RefreshButton from './RefreshButton' import { ContactButton } from './ContactButton' import { info_circle } from '@equinor/eds-icons' import { StyledLink } from './styles' +import CreateProduct from './CreateProduct' const Navbar = () => { return ( @@ -14,7 +15,7 @@ const Navbar = () => {
@@ -30,6 +31,7 @@ const Navbar = () => {
+
diff --git a/web/src/Types.ts b/web/src/Types.ts index 73ab73a..a8f122e 100644 --- a/web/src/Types.ts +++ b/web/src/Types.ts @@ -13,6 +13,12 @@ export interface Product { cumulative: Array | null } +export type TNewProduct = { + productName: string + productSupplier: string + productData: number[][] +} + export interface Products { [id: string]: Product } diff --git a/web/yarn.lock b/web/yarn.lock index 967a445..8582c60 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1116,7 +1116,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.8", "@babel/runtime@^7.24.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== @@ -1290,35 +1290,35 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== -"@equinor/eds-core-react@^0.34.0": - version "0.34.0" - resolved "https://registry.yarnpkg.com/@equinor/eds-core-react/-/eds-core-react-0.34.0.tgz#ca965022851bf6d7565bb210d063a5368774b692" - integrity sha512-hkAuNhXk0/PylpEEtRobt3qeZyK7Hhu27pxMV6Y+StKi7ckuH7LJEisRAXxbUchFiv2tzNV7zxhdoPd5GjRp4w== +"@equinor/eds-core-react@^0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@equinor/eds-core-react/-/eds-core-react-0.36.1.tgz#b8f905f01976150bc4e6e56b427b25ba510ffa2e" + integrity sha512-cFpmsT4+EEFDhGE1DLNDT9Scr6SNBF4xnIfAgkMZcK6wmmZZT30lV2zdGgFC1JN9FfyvlisQukgpurynuBoJTw== dependencies: - "@babel/runtime" "^7.23.2" - "@equinor/eds-icons" "^0.19.3" + "@babel/runtime" "^7.24.0" + "@equinor/eds-icons" "^0.21.0" "@equinor/eds-tokens" "0.9.2" - "@equinor/eds-utils" "0.8.3" - "@floating-ui/react" "^0.26.2" - "@tanstack/react-virtual" "3.0.0-beta.54" - downshift "^8.2.3" + "@equinor/eds-utils" "0.8.4" + "@floating-ui/react" "^0.26.9" + "@tanstack/react-virtual" "3.1.3" + downshift "8.3.3" -"@equinor/eds-icons@^0.19.3": - version "0.19.3" - resolved "https://registry.yarnpkg.com/@equinor/eds-icons/-/eds-icons-0.19.3.tgz#08ce80c1ea6edf0b4144def73d1e5d0de392434f" - integrity sha512-Sh0W01PrwXPCi8/p9YKj0qKNtRU9R/xYJORinIzsNNRllpiu9wvuGAsQNE0gQaDDnrprsiRBH3+MdMSRXVs3Wg== +"@equinor/eds-icons@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@equinor/eds-icons/-/eds-icons-0.21.0.tgz#9adb994d2cd74011474a6d354458ffedcf7afc2f" + integrity sha512-k2keACHou9h9D5QLfSBeojTApqbPCkHNBWplUA/B9FQv8FMCMSBbjJAo2L/3yAExMylQN9LdwKo81T2tijRXoA== "@equinor/eds-tokens@0.9.2", "@equinor/eds-tokens@^0.9.2": version "0.9.2" resolved "https://registry.yarnpkg.com/@equinor/eds-tokens/-/eds-tokens-0.9.2.tgz#21545ffbb16a22f3eb7370295d32276dcb655373" integrity sha512-pDIFei0vsfN3GN12NKWqxskAkYBQd6+Dzjga2liuY81LfnlJs5g9NblamU9WY5w5YdVE5Z8FNjsMKDLs2JIWcw== -"@equinor/eds-utils@0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@equinor/eds-utils/-/eds-utils-0.8.3.tgz#c13db2f6a738a6d21206acd8ddb06a77c0cbf841" - integrity sha512-+Xm+BsXMUqlxZpPeGAJExhf27WqcQDHmKY9e4s6hfFP8D2vj5VcS0cglY86cmH4N3bDb4LmHL8V2FSX9xHr9+g== +"@equinor/eds-utils@0.8.4": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@equinor/eds-utils/-/eds-utils-0.8.4.tgz#f2c33c4a04784aaff2a2b42f52b247312e8cd1dd" + integrity sha512-njvqXd3Hzfy5vkEqnx+uEBAu00vnG/5R+gDgWCReVDjjUoHdQNcrqfjBLsGF2UungtO0LbYV8YuBP+9l4V7ywQ== dependencies: - "@babel/runtime" "^7.23.2" + "@babel/runtime" "^7.23.8" "@equinor/eds-tokens" "0.9.2" "@eslint-community/eslint-utils@^4.2.0": @@ -1353,41 +1353,41 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6" integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA== -"@floating-ui/core@^1.4.2": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.1.tgz#62707d7ec585d0929f882321a1b1f4ea9c680da5" - integrity sha512-QgcKYwzcc8vvZ4n/5uklchy8KVdjJwcOeI+HnnTNclJjs2nYsy23DOCf+sSV1kBwD9yDAoVKCkv/gEPzgQU3Pw== +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== dependencies: - "@floating-ui/utils" "^0.1.3" + "@floating-ui/utils" "^0.2.1" -"@floating-ui/dom@^1.5.1": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa" - integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== dependencies: - "@floating-ui/core" "^1.4.2" - "@floating-ui/utils" "^0.1.3" + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" -"@floating-ui/react-dom@^2.0.3": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.4.tgz#b076fafbdfeb881e1d86ae748b7ff95150e9f3ec" - integrity sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ== +"@floating-ui/react-dom@^2.0.0": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== dependencies: - "@floating-ui/dom" "^1.5.1" + "@floating-ui/dom" "^1.6.1" -"@floating-ui/react@^0.26.2": - version "0.26.3" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.3.tgz#1ec435f35e37d5e34577ee89c7abb1eedb3a0c5d" - integrity sha512-iKH8WRR0L/nLiM6qavFZxkyegIZRMxGnM9aKEc71M4wRlUNkgTamjPsOQXy11oZbDOH37MiTbk/nAPn9M2+shA== +"@floating-ui/react@^0.26.9": + version "0.26.11" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.11.tgz#226d3fec890de439443b62f3138ef7de052b0998" + integrity sha512-fo01Cu+jzLDVG/AYAV2OtV6flhXvxP5rDaR1Fk8WWhtsFqwk478Dr2HGtB8s0HqQCsFWVbdHYpPjMiQiR/A9VA== dependencies: - "@floating-ui/react-dom" "^2.0.3" - "@floating-ui/utils" "^0.1.5" - tabbable "^6.0.1" + "@floating-ui/react-dom" "^2.0.0" + "@floating-ui/utils" "^0.2.0" + tabbable "^6.0.0" -"@floating-ui/utils@^0.1.3", "@floating-ui/utils@^0.1.5": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" - integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== "@humanwhocodes/config-array@^0.11.13": version "0.11.13" @@ -1942,17 +1942,17 @@ dependencies: tslib "^2.4.0" -"@tanstack/react-virtual@3.0.0-beta.54": - version "3.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.54.tgz#755979455adf13f2584937204a3f38703e446037" - integrity sha512-D1mDMf4UPbrtHRZZriCly5bXTBMhylslm4dhcHqTtDJ6brQcgGmk8YD9JdWBGWfGSWPKoh2x1H3e7eh+hgPXtQ== +"@tanstack/react-virtual@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.1.3.tgz#4ef2a7dd819a7dd2b634d50cbd6ba498f06529ec" + integrity sha512-YCzcbF/Ws/uZ0q3Z6fagH+JVhx4JLvbSflgldMgLsuvB8aXjZLLb3HvrEVxY480F9wFlBiXlvQxOyXb5ENPrNA== dependencies: - "@tanstack/virtual-core" "3.0.0-beta.54" + "@tanstack/virtual-core" "3.1.3" -"@tanstack/virtual-core@3.0.0-beta.54": - version "3.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.54.tgz#12259d007911ad9fce1388385c54a9141f4ecdc4" - integrity sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g== +"@tanstack/virtual-core@3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.1.3.tgz#77ced625f19ec9350f6e460f142b3be9bff03866" + integrity sha512-Y5B4EYyv1j9V8LzeAoOVeTg0LI7Fo5InYKgAjkY1Pu9GjtUwX/EKxNcU7ng3sKr99WEf+bPTcktAeybyMOYo+g== "@tootallnate/once@1": version "1.1.2" @@ -4199,10 +4199,10 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -downshift@^8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/downshift/-/downshift-8.2.3.tgz#27106a5d9f408a6f6f9350ca465801d07e52db87" - integrity sha512-1HkvqaMTZpk24aqnXaRDnT+N5JCbpFpW+dCogB11+x+FCtfkFX0MbAO4vr/JdXi1VYQF174KjNUveBXqaXTPtg== +downshift@8.3.3: + version "8.3.3" + resolved "https://registry.yarnpkg.com/downshift/-/downshift-8.3.3.tgz#fef2804f09ffd013076f2dec6829559083c6c54f" + integrity sha512-f9znQFYF/3AWBkFiEc4H05Vdh41XFgJ80IatLBKIFoA3p86mAXc/iM9/XJ24loF9djtABD5NBEYL7b1b7xh2pw== dependencies: "@babel/runtime" "^7.22.15" compute-scroll-into-view "^3.0.3" @@ -8043,7 +8043,7 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-oauth2-code-pkce@^1.15.2: +react-oauth2-code-pkce@^1.17.2: version "1.17.2" resolved "https://registry.yarnpkg.com/react-oauth2-code-pkce/-/react-oauth2-code-pkce-1.17.2.tgz#a36fff30e33c131ed6bcdeeb7094536afad88448" integrity sha512-CIuvKlOr0r390NTvtEXSTCrNuGz7pu+Omf3LOcsEGuhvuLze3G4i7RjYVsqLjWALdvy40Wv3q5PV+qTbIryBoQ== @@ -9087,7 +9087,7 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -tabbable@^6.0.1: +tabbable@^6.0.0: version "6.2.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==