Skip to content

Commit

Permalink
Pull request update/241107
Browse files Browse the repository at this point in the history
6245f70 OS-7838. Fixes for manual data source report reimport
4120b0b OS-7838. Add support for manual data source billing reimport
d918331 OS-7945. Updated SQLAlchemy version
a443b52 OS-7938: Added recommendation filtering by cloud services
  • Loading branch information
stanfra authored Nov 7, 2024
2 parents 45fed41 + 6245f70 commit 5b88b61
Show file tree
Hide file tree
Showing 58 changed files with 501 additions and 137 deletions.
2 changes: 2 additions & 0 deletions ngui/server/api/restapi/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class RestClient extends BaseClient {
const dataSource = await this.patch(path, {
body: JSON.stringify({
name: params.name,
last_import_at: params.lastImportAt,
last_import_modified_at: params.lastImportModifiedAt,
config: {
...params.awsRootConfig,
...params.awsLinkedConfig,
Expand Down
2 changes: 2 additions & 0 deletions ngui/server/graphql/resolvers/restapi.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ export type UpdateDataSourceInput = {
databricksConfig?: InputMaybe<DatabricksConfigInput>;
gcpConfig?: InputMaybe<GcpConfigInput>;
k8sConfig?: InputMaybe<K8sConfigInput>;
lastImportAt?: InputMaybe<Scalars['Int']['input']>;
lastImportModifiedAt?: InputMaybe<Scalars['Int']['input']>;
name?: InputMaybe<Scalars['String']['input']>;
nebiusConfig?: InputMaybe<NebiusConfigInput>;
};
Expand Down
2 changes: 2 additions & 0 deletions ngui/server/graphql/schemas/restapi.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ input DatabricksConfigInput {

input UpdateDataSourceInput {
name: String
lastImportAt: Int
lastImportModifiedAt: Int
awsRootConfig: AwsRootConfigInput
awsLinkedConfig: AwsLinkedConfigInput
azureSubscriptionConfig: AzureSubscriptionConfigInput
Expand Down
49 changes: 45 additions & 4 deletions ngui/ui/src/components/CloudAccountDetails/CloudAccountDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CloudDownloadOutlinedIcon from "@mui/icons-material/CloudDownloadOutlined";
import PowerOffOutlinedIcon from "@mui/icons-material/PowerOffOutlined";
import SettingsIcon from "@mui/icons-material/Settings";
import { Link } from "@mui/material";
Expand All @@ -13,7 +14,8 @@ import {
DisconnectCloudAccountModal,
UpdateDataSourceCredentialsModal,
RenameDataSourceModal,
KubernetesIntegrationModal
KubernetesIntegrationModal,
DataSourceBillingReimportModal
} from "components/SideModalManager/SideModals";
import SummaryGrid from "components/SummaryGrid";
import TabsWrapper from "components/TabsWrapper";
Expand All @@ -34,7 +36,11 @@ import {
CLOUD_ACCOUNT_DETAILS_PAGE_TABS,
ENVIRONMENT,
AZURE_TENANT,
DATABRICKS
DATABRICKS,
AZURE_CNR,
GCP_CNR,
ALIBABA_CNR,
NEBIUS
} from "utils/constants";
import { summarizeChildrenDetails } from "utils/dataSources";
import { SPACING_2 } from "utils/layouts";
Expand All @@ -48,7 +54,7 @@ const {
PRICING: PRICING_TAB
} = CLOUD_ACCOUNT_DETAILS_PAGE_TABS;

const PageActionBar = ({ id, type, parentId, name, config, isLoading }) => {
const PageActionBar = ({ id, type, parentId, name, config, lastImportAt, isLoading }) => {
const { isDemo } = useOrganizationInfo();
const openSideModal = useOpenSideModal();

Expand All @@ -62,6 +68,32 @@ const PageActionBar = ({ id, type, parentId, name, config, isLoading }) => {
return [];
}

const getBillingReimportButton = () => {
const hasPreviousImport = lastImportAt !== 0;

const isEligibleForReimport =
(type === AWS_CNR && !config.linked) || [AZURE_CNR, GCP_CNR, ALIBABA_CNR, NEBIUS].includes(type);

return {
show: isEligibleForReimport,
getItem: () => ({
key: "cloudAccountDetails-reimport-expenses",
icon: <CloudDownloadOutlinedIcon fontSize="small" />,
messageId: "billingReimportTitle",
dataTestId: "btn_expenses_reimport_data_source_modal",
type: "button",
isLoading,
action: () => openSideModal(DataSourceBillingReimportModal, { name, id, type, config }),
requiredActions: ["MANAGE_CLOUD_CREDENTIALS"],
disabled: !hasPreviousImport,
tooltip: {
show: !hasPreviousImport,
value: <FormattedMessage id="dataSourceNoBillingReportsProcessedYet" />
}
})
};
};

return [
{
show: type === KUBERNETES_CNR,
Expand Down Expand Up @@ -110,6 +142,7 @@ const PageActionBar = ({ id, type, parentId, name, config, isLoading }) => {
}
})
},
getBillingReimportButton(),
{
show: type !== ENVIRONMENT,
getItem: () => ({
Expand Down Expand Up @@ -340,7 +373,15 @@ const CloudAccountDetails = ({ data = {}, isLoading = false }) => {

return (
<>
<PageActionBar id={id} type={type} name={name} parentId={parentId} config={config} isLoading={isLoading} />
<PageActionBar
id={id}
type={type}
name={name}
parentId={parentId}
config={config}
lastImportAt={lastImportAt}
isLoading={isLoading}
/>
<PageContentWrapper>
<Grid container spacing={SPACING_2}>
<Grid item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ import {
getMinutes,
setMinutes,
roundTimeToInterval,
subMinutes
subMinutes,
startOfDay,
endOfDay
} from "utils/datetime";
import IntervalTimeSelectors from "./IntervalTimeSelectors";

// TODO: here and in RangePicker — apply more descriptive naming for whole pickers structure
const IntervalTimePopoverContent = ({
open,
onDayClick,
initialDate = +new Date(),
initialDate: initialDateProp = +new Date(),
minDate,
maxDate,
intervalMinutes = AMOUNT_30_MINUTES,
Expand All @@ -31,22 +33,33 @@ const IntervalTimePopoverContent = ({
const { classes, cx } = useStyles();
const today = new Date();

const initialDateRounded =
/**
* Round to the farthest minute to prevent date jumping to the next day
* E.g if the initial datetime is equal to "23:59" and intervalMinutes is set to "30"
* it should be rounded to 23:30, not to 00:00 of the next day
*/
getHours(initialDate) === 23
? roundTimeToInterval(subMinutes(initialDate, intervalMinutes), intervalMinutes)
: roundTimeToInterval(initialDate, intervalMinutes);
const minDateValid = +startOfDay(parseOptionalDate(minDate, MIN_PICKER_DATE));
const maxDateValid = +endOfDay(parseOptionalDate(maxDate, today));

const minDateValid = parseOptionalDate(minDate, MIN_PICKER_DATE);
const maxDateValid = parseOptionalDate(maxDate, today);
const initialFirstMonth = initialDateRounded;
const getInitialDate = () => {
const allowedRangeInitialDate = Math.min(Math.max(minDateValid, initialDateProp), maxDateValid);

const [date, setDate] = useState(initialDateRounded);
const [monthToShow, setMonthToShow] = useState(getTime(initialFirstMonth || today));
const initialDateRounded =
/**
* Round to the farthest minute to prevent date jumping to the next day
* E.g if the initial datetime is equal to "23:59" and intervalMinutes is set to "30"
* it should be rounded to 23:30, not to 00:00 of the next day
*/
getHours(allowedRangeInitialDate) === 23
? roundTimeToInterval(subMinutes(allowedRangeInitialDate, intervalMinutes), intervalMinutes)
: roundTimeToInterval(allowedRangeInitialDate, intervalMinutes);

return initialDateRounded;
};

const initialDate = getInitialDate();

const [date, setDate] = useState(initialDate);
const [monthToShow, setMonthToShow] = useState(() => {
const initialMonthDate = initialDate || +today;

return getTime(Math.min(initialMonthDate, maxDateValid));
});

// handlers
// set month to show from header selectors
Expand All @@ -59,14 +72,14 @@ const IntervalTimePopoverContent = ({
useEffect(() => {
if (initialMount.current) {
initialMount.current = false;
onDayClick(initialDateRounded);
onDayClick(initialDate);
}
});

// set clicked day — applying rounded hours and minutes to selected date (Month returns start of the day)
const onDayClickHandler = (day) => {
const initialDateHours = +getHours(initialDateRounded);
const initialDateMinutes = +getMinutes(initialDateRounded);
const initialDateHours = +getHours(initialDate);
const initialDateMinutes = +getMinutes(initialDate);

const dateWithInitialTime = setHours(setMinutes(day, initialDateMinutes), initialDateHours);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const MlTaskRecommendations = ({ taskId, recommendations, isLoading }) => {
onRecommendationClick={onRecommendationClick}
isDownloadAvailable={isDownloadAvailable}
isGetIsDownloadAvailableLoading={isGetIsDownloadAvailableLoading}
selectedDataSources={[]}
selectedDataSourceIds={[]}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import DataSourceBillingReimportContainer from "containers/DataSourceBillingReimportContainer/DataSourceBillingReimportContainer";
import BaseSideModal from "./BaseSideModal";

class DataSourceBillingReimportModal extends BaseSideModal {
headerProps = {
messageId: "billingReimportTitle",
color: "primary",
dataTestIds: {
title: "lbl_reimport_data_source_expenses",
closeButton: "btn_close"
}
};

dataTestId = "smodal_reimport_data_source_expenses";

get content() {
return <DataSourceBillingReimportContainer dataSourceId={this.payload?.id} onSuccess={this.closeSideModal} />;
}
}

export default DataSourceBillingReimportModal;
4 changes: 3 additions & 1 deletion ngui/ui/src/components/SideModalManager/SideModals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CreateOrganizationModal from "./CreateOrganizationModal";
import CreateOrganizationOptionModal from "./CreateOrganizationOptionModal";
import CreateResourcePerspectiveModal from "./CreateResourcePerspectiveModal";
import CreateS3DuplicateFinderCheckModal from "./CreateS3DuplicateFinderCheckModal";
import DataSourceBillingReimportModal from "./DataSourceBillingReimportModal";
import DeleteAssignmentRuleModal from "./DeleteAssignmentRuleModal";
import DeleteBIExportModal from "./DeleteBIExportModal";
import DeleteClusterTypeModal from "./DeleteClusterTypeModal";
Expand Down Expand Up @@ -133,5 +134,6 @@ export {
EditModelVersionAliasModal,
EditModelPathModal,
EditModelVersionTagsModal,
MlDeleteArtifactModal
MlDeleteArtifactModal,
DataSourceBillingReimportModal
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createRoot } from "react-dom/client";
import TestProvider from "tests/TestProvider";
import DataSourceBillingReimportForm from "./DataSourceBillingReimportForm";

it("renders without crashing", () => {
const div = document.createElement("div");
const root = createRoot(div);
root.render(
<TestProvider>
<DataSourceBillingReimportForm onSubmit={vi.fn} />
</TestProvider>
);
root.unmount();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Stack } from "@mui/material";
import { FormProvider, useForm } from "react-hook-form";
import InlineSeverityAlert from "components/InlineSeverityAlert";
import { SPACING_1 } from "utils/layouts";
import { FormButtons, ReimportFromDatePicker } from "./FormElements";
import { DataSourceBillingReimportFormProps, FormValues } from "./types";
import { getDefaultValues } from "./utils";

const DataSourceBillingReimportForm = ({ onSubmit, isSubmitLoading = false }: DataSourceBillingReimportFormProps) => {
const methods = useForm<FormValues>({
defaultValues: getDefaultValues()
});

const { handleSubmit } = methods;

return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<Stack spacing={SPACING_1}>
<ReimportFromDatePicker />
<InlineSeverityAlert messageId="billingReimportWarning" severity="warning" />
</Stack>
<FormButtons isLoading={isSubmitLoading} />
</form>
</FormProvider>
);
};

export default DataSourceBillingReimportForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import FormButtonsWrapper from "components/FormButtonsWrapper";
import SubmitButtonLoader from "components/SubmitButtonLoader";

const FormButtons = ({ isLoading = false }) => (
<FormButtonsWrapper>
<SubmitButtonLoader
messageId="scheduleImport"
isLoading={isLoading}
dataTestId="btn_confirm"
loaderDataTestId="btn_confirm_loader"
/>
</FormButtonsWrapper>
);

export default FormButtons;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Controller, useFormContext } from "react-hook-form";
import { useIntl } from "react-intl";
import IntervalTimePicker from "components/IntervalTimePicker";
import { EN_FORMAT, startOfMonth, subDays, subYears } from "utils/datetime";
import { FIELD_NAMES } from "../constants";

const FIELD_NAME = FIELD_NAMES.IMPORT_FROM;

const ReimportFromDatePicker = () => {
const intl = useIntl();

const {
control,
formState: { errors }
} = useFormContext();

return (
<Controller
name={FIELD_NAME}
control={control}
rules={{
required: {
value: true,
message: intl.formatMessage({ id: "thisFieldIsRequired" })
}
}}
render={({ field: { value, onChange } }) => (
<IntervalTimePicker
labelMessageId="importFrom"
value={value}
required
notSetMessageId="notSet"
onApply={onChange}
fullWidth
format={EN_FORMAT}
margin="dense"
minDate={+subYears(new Date(), 1)}
maxDate={+subDays(startOfMonth(new Date()), 1)}
validation={{
dataTestId: `input_${FIELD_NAME}`,
error: !!errors[FIELD_NAME],
helperText: errors[FIELD_NAME]?.message
}}
dataTestIds={{
field: {
input: `input_${FIELD_NAME}`,
iconButton: "btn_select_date"
}
}}
/>
)}
/>
);
};

export default ReimportFromDatePicker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import FormButtons from "./FormButtons";
import ReimportFromDatePicker from "./ReimportFromDatePicker";

export { ReimportFromDatePicker, FormButtons };
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const FIELD_NAMES = Object.freeze({
IMPORT_FROM: "importFrom"
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DataSourceBillingReimportForm from "./DataSourceBillingReimportForm";

export default DataSourceBillingReimportForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FIELD_NAMES } from "./constants";

export type FormValues = {
[FIELD_NAMES.IMPORT_FROM]: number;
};

export type DataSourceBillingReimportFormProps = {
onSubmit: (data: FormValues) => void;
isSubmitLoading?: boolean;
};
Loading

0 comments on commit 5b88b61

Please sign in to comment.