Transforms: Add 'Format String' Transform (#73624)

* Format String Implementation

* Prettier

* Unify Every/Pascal/Camel cases

* Reformat + add new cases

* Add Trim and Substring to the transform options

* Trim/Substring tests+formatting

* refactor

* docs + feature toggle

* add category

* docs. add svg. change description

* revert weird add from merge

* readd config. change description

* docs change

* Adding experimental shortcode

* Add code formatting

* change shortcode

---------

Co-authored-by: Victor Marin <victor.marin@grafana.com>
Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com>
This commit is contained in:
Sol 2023-10-13 19:17:12 +01:00 committed by GitHub
parent 48ef88aed7
commit 889576ac1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 408 additions and 1 deletions

View File

@ -1007,6 +1007,23 @@ This transformation is available in Grafana 10.1+ as an alpha feature.
Use this transformation to format the output of a time field. Output can be formatted using (Moment.js format strings)[https://momentjs.com/docs/#/displaying/]. For instance, if you would like to display only the year of a time field the format string `YYYY` can be used to show the calendar year (e.g. 1999, 2012, etc.).
### Format string
> **Note:** This transformation is an experimental feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `formatString` in Grafana to use this feature. Contact Grafana Support to enable this feature in Grafana Cloud.
Use this transformation to format the output of a string field. You can format output in the following ways:
- Upper case - Formats the entire string in upper case characters.
- Lower case - Formats the entire string in lower case characters.
- Sentence case - Formats the the first character of the string in upper case.
- Title case - Formats the first character of each word in the string in upper case.
- Pascal case - Formats the first character of each word in the string in upper case and doesn't include spaces between words.
- Camel case - Formats the first character of each word in the string in upper case, except the first word, and doesn't include spaces between words.
- Snake case - Formats all characters in the string in lower case and uses underscores instead of spaces between words.
- Kebab case - Formats all characters in the string in lower case and uses dashes instead of spaces between words.
- Trim - Removes all leading and trailing spaces from the string.
- Substring - Returns a substring of the string, using the specified start and end positions.
{{% docs/reference %}}
[Table panel]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/visualizations/table"
[Table panel]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/visualizations/table"

View File

@ -143,6 +143,7 @@ Experimental features might be changed or removed without prior notice.
| `httpSLOLevels` | Adds SLO level to http request metrics |
| `panelMonitoring` | Enables panel monitoring through logs and measurements |
| `enableNativeHTTPHistogram` | Enables native HTTP Histograms |
| `formatString` | Enable format string transformer |
| `transformationsVariableSupport` | Allows using variables in transformations |
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists |
| `navAdminSubsections` | Splits the administration section of the nav tree into subsections |

View File

@ -6,6 +6,7 @@ import { filterFieldsTransformer, filterFramesTransformer } from './transformers
import { filterFieldsByNameTransformer } from './transformers/filterByName';
import { filterFramesByRefIdTransformer } from './transformers/filterByRefId';
import { filterByValueTransformer } from './transformers/filterByValue';
import { formatStringTransformer } from './transformers/formatString';
import { formatTimeTransformer } from './transformers/formatTime';
import { groupByTransformer } from './transformers/groupBy';
import { groupingToMatrixTransformer } from './transformers/groupingToMatrix';
@ -30,6 +31,7 @@ export const standardTransformers = {
filterFramesTransformer,
filterFramesByRefIdTransformer,
filterByValueTransformer,
formatStringTransformer,
formatTimeTransformer,
orderFieldsTransformer,
organizeFieldsTransformer,

View File

@ -0,0 +1,85 @@
import { toDataFrame } from '../../dataframe/processDataFrame';
import { FieldType } from '../../types/dataFrame';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
import { fieldMatchers } from '../matchers';
import { FieldMatcherID } from '../matchers/ids';
import {
createStringFormatter,
FormatStringOutput,
formatStringTransformer,
getFormatStringFunction,
} from './formatString';
const frame = toDataFrame({
fields: [
{
name: 'names',
type: FieldType.string,
values: ['alice', 'BOB', ' charliE ', 'david frederick attenborough', 'Emma Fakename', ''],
},
],
});
const fieldMatches = fieldMatchers.get(FieldMatcherID.byName).get('names');
const options = (format: FormatStringOutput, substringStart?: number, substringEnd?: number) => {
return {
stringField: 'names',
substringStart: substringStart ?? 0,
substringEnd: substringEnd ?? 100,
outputFormat: format,
};
};
describe('Format String Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([formatStringTransformer]);
});
it('will convert string to each case', () => {
const formats = [
FormatStringOutput.UpperCase,
FormatStringOutput.LowerCase,
FormatStringOutput.SentenceCase,
FormatStringOutput.TitleCase,
FormatStringOutput.PascalCase,
FormatStringOutput.CamelCase,
FormatStringOutput.SnakeCase,
FormatStringOutput.KebabCase,
FormatStringOutput.Trim,
];
const newValues = [];
for (let i = 0; i < formats.length; i++) {
const formatter = createStringFormatter(fieldMatches, getFormatStringFunction(options(formats[i])));
const newFrame = formatter(frame, [frame]);
newValues.push(newFrame[0].values);
}
const answers = [
['ALICE', 'BOB', ' CHARLIE ', 'DAVID FREDERICK ATTENBOROUGH', 'EMMA FAKENAME', ''], // Upper Case
['alice', 'bob', ' charlie ', 'david frederick attenborough', 'emma fakename', ''], // Lower Case
['Alice', 'BOB', ' charliE ', 'David frederick attenborough', 'Emma Fakename', ''], // Sentence Case
['Alice', 'Bob', ' Charlie ', 'David Frederick Attenborough', 'Emma Fakename', ''], // Title Case
['Alice', 'Bob', 'Charlie', 'DavidFrederickAttenborough', 'EmmaFakename', ''], // Pascal Case
['alice', 'bob', 'charlie', 'davidFrederickAttenborough', 'emmaFakename', ''], // Camel Case
['alice', 'bob', '__charlie__', 'david_frederick_attenborough', 'emma_fakename', ''], // Snake Case
['alice', 'bob', '--charlie--', 'david-frederick-attenborough', 'emma-fakename', ''], // Kebab Case
['alice', 'BOB', 'charliE', 'david frederick attenborough', 'Emma Fakename', ''], // Trim
];
expect(newValues).toEqual(answers);
});
it('will convert string to substring', () => {
const formatter = createStringFormatter(
fieldMatches,
getFormatStringFunction(options(FormatStringOutput.Substring, 2, 5))
);
const newFrame = formatter(frame, [frame]);
const newValues = newFrame[0].values;
expect(newValues).toEqual(['ice', 'B', 'cha', 'vid', 'ma ', '']);
});
});

View File

@ -0,0 +1,112 @@
import { map } from 'rxjs/operators';
import { DataFrame, Field, FieldType } from '../../types';
import { DataTransformerInfo, FieldMatcher } from '../../types/transformations';
import { fieldMatchers } from '../matchers';
import { FieldMatcherID } from '../matchers/ids';
import { DataTransformerID } from './ids';
export enum FormatStringOutput {
UpperCase = 'Upper Case',
LowerCase = 'Lower Case',
SentenceCase = 'Sentence Case',
TitleCase = 'Title Case',
PascalCase = 'Pascal Case',
CamelCase = 'Camel Case',
SnakeCase = 'Snake Case',
KebabCase = 'Kebab Case',
Trim = 'Trim',
Substring = 'Substring',
}
export interface FormatStringTransformerOptions {
stringField: string;
substringStart: number;
substringEnd: number;
outputFormat: FormatStringOutput;
}
const splitToCapitalWords = (input: string) => {
const arr = input.split(' ');
for (let i = 0; i < arr.length; i++) {
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1).toLowerCase();
}
return arr;
};
export const getFormatStringFunction = (options: FormatStringTransformerOptions) => {
return (field: Field) =>
field.values.map((value: string) => {
switch (options.outputFormat) {
case FormatStringOutput.UpperCase:
return value.toUpperCase();
case FormatStringOutput.LowerCase:
return value.toLowerCase();
case FormatStringOutput.SentenceCase:
return value.charAt(0).toUpperCase() + value.slice(1);
case FormatStringOutput.TitleCase:
return splitToCapitalWords(value).join(' ');
case FormatStringOutput.PascalCase:
return splitToCapitalWords(value).join('');
case FormatStringOutput.CamelCase:
value = splitToCapitalWords(value).join('');
return value.charAt(0).toLowerCase() + value.slice(1);
case FormatStringOutput.SnakeCase:
return value.toLowerCase().split(' ').join('_');
case FormatStringOutput.KebabCase:
return value.toLowerCase().split(' ').join('-');
case FormatStringOutput.Trim:
return value.trim();
case FormatStringOutput.Substring:
return value.substring(options.substringStart, options.substringEnd);
}
});
};
export const formatStringTransformer: DataTransformerInfo<FormatStringTransformerOptions> = {
id: DataTransformerID.formatString,
name: 'Format string',
description: 'Manipulate string fields formatting',
defaultOptions: { stringField: '', outputFormat: FormatStringOutput.UpperCase },
operator: (options) => (source) =>
source.pipe(
map((data) => {
if (data.length === 0) {
return data;
}
const fieldMatches = fieldMatchers.get(FieldMatcherID.byName).get(options.stringField);
const formatStringFunction = getFormatStringFunction(options);
const formatter = createStringFormatter(fieldMatches, formatStringFunction);
return data.map((frame) => ({
...frame,
fields: formatter(frame, data),
}));
})
),
};
/**
* @internal
*/
export const createStringFormatter =
(fieldMatches: FieldMatcher, formatStringFunction: (field: Field) => string[]) =>
(frame: DataFrame, allFrames: DataFrame[]) => {
return frame.fields.map((field) => {
// Find the configured field
if (fieldMatches(field, frame, allFrames)) {
const newVals = formatStringFunction(field);
return {
...field,
type: FieldType.string,
values: newVals,
};
}
return field;
});
};

View File

@ -16,7 +16,7 @@ export interface FormatTimeTransformerOptions {
export const formatTimeTransformer: DataTransformerInfo<FormatTimeTransformerOptions> = {
id: DataTransformerID.formatTime,
name: 'Format Time',
name: 'Format time',
description: 'Set the output format of a time field',
defaultOptions: { timeField: '', outputFormat: '', useTimezone: true },
isApplicable: (data: DataFrame[]) => {

View File

@ -38,4 +38,5 @@ export enum DataTransformerID {
partitionByValues = 'partitionByValues',
timeSeriesTable = 'timeSeriesTable',
formatTime = 'formatTime',
formatString = 'formatString',
}

View File

@ -136,6 +136,7 @@ export interface FeatureToggles {
externalServiceAccounts?: boolean;
panelMonitoring?: boolean;
enableNativeHTTPHistogram?: boolean;
formatString?: boolean;
transformationsVariableSupport?: boolean;
kubernetesPlaylists?: boolean;
navAdminSubsections?: boolean;

View File

@ -826,6 +826,13 @@ var (
FrontendOnly: false,
Owner: hostedGrafanaTeam,
},
{
Name: "formatString",
Description: "Enable format string transformer",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaBiSquad,
},
{
Name: "transformationsVariableSupport",
Description: "Allows using variables in transformations",

View File

@ -117,6 +117,7 @@ cloudWatchWildCardDimensionValues,GA,@grafana/aws-datasources,false,false,false,
externalServiceAccounts,experimental,@grafana/grafana-authnz-team,true,false,false,false
panelMonitoring,experimental,@grafana/dataviz-squad,false,false,false,true
enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,false,false,false,false
formatString,experimental,@grafana/grafana-bi-squad,false,false,false,true
transformationsVariableSupport,experimental,@grafana/grafana-bi-squad,false,false,false,true
kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,false,false,false,true
navAdminSubsections,experimental,@grafana/grafana-frontend-platform,false,false,false,false

1 Name Stage Owner requiresDevMode RequiresLicense RequiresRestart FrontendOnly
117 externalServiceAccounts experimental @grafana/grafana-authnz-team true false false false
118 panelMonitoring experimental @grafana/dataviz-squad false false false true
119 enableNativeHTTPHistogram experimental @grafana/hosted-grafana-team false false false false
120 formatString experimental @grafana/grafana-bi-squad false false false true
121 transformationsVariableSupport experimental @grafana/grafana-bi-squad false false false true
122 kubernetesPlaylists experimental @grafana/grafana-app-platform-squad false false false true
123 navAdminSubsections experimental @grafana/grafana-frontend-platform false false false false

View File

@ -479,6 +479,10 @@ const (
// Enables native HTTP Histograms
FlagEnableNativeHTTPHistogram = "enableNativeHTTPHistogram"
// FlagFormatString
// Enable format string transformer
FlagFormatString = "formatString"
// FlagTransformationsVariableSupport
// Allows using variables in transformations
FlagTransformationsVariableSupport = "transformationsVariableSupport"

View File

@ -0,0 +1,123 @@
import React, { useCallback } from 'react';
import {
DataTransformerID,
SelectableValue,
standardTransformers,
TransformerRegistryItem,
TransformerUIProps,
PluginState,
FieldType,
StandardEditorsRegistryItem,
FieldNamePickerConfigSettings,
TransformerCategory,
} from '@grafana/data';
import {
FormatStringOutput,
FormatStringTransformerOptions,
} from '@grafana/data/src/transformations/transformers/formatString';
import { Select, InlineFieldRow, InlineField } from '@grafana/ui';
import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker';
import { NumberInput } from 'app/core/components/OptionsUI/NumberInput';
const fieldNamePickerSettings: StandardEditorsRegistryItem<string, FieldNamePickerConfigSettings> = {
settings: {
width: 30,
filter: (f) => f.type === FieldType.string,
placeholderText: 'Select text field',
noFieldsMessage: 'No text fields found',
},
name: '',
id: '',
editor: () => null,
};
function FormatStringTransfomerEditor({
input,
options,
onChange,
}: TransformerUIProps<FormatStringTransformerOptions>) {
const onSelectField = useCallback(
(value: string | undefined) => {
const val = value ?? '';
onChange({
...options,
stringField: val,
});
},
[onChange, options]
);
const onFormatChange = useCallback(
(value: SelectableValue<FormatStringOutput>) => {
const val = value.value ?? FormatStringOutput.UpperCase;
onChange({
...options,
outputFormat: val,
});
},
[onChange, options]
);
const onSubstringStartChange = useCallback(
(value?: number) => {
onChange({
...options,
substringStart: value ?? 0,
});
},
[onChange, options]
);
const onSubstringEndChange = useCallback(
(value?: number) => {
onChange({
...options,
substringEnd: value ?? 0,
});
},
[onChange, options]
);
const ops = Object.values(FormatStringOutput).map((value) => ({ label: value, value }));
return (
<>
<InlineFieldRow>
<InlineField label={'Field'} labelWidth={10}>
<FieldNamePicker
context={{ data: input }}
value={options.stringField ?? ''}
onChange={onSelectField}
item={fieldNamePickerSettings}
/>
</InlineField>
<InlineField label="Format" labelWidth={10}>
<Select options={ops} value={options.outputFormat} onChange={onFormatChange} width={20} />
</InlineField>
</InlineFieldRow>
{options.outputFormat === FormatStringOutput.Substring && (
<InlineFieldRow>
<InlineField label="Substring range" labelWidth={15}>
<NumberInput min={0} value={options.substringStart ?? 0} onChange={onSubstringStartChange} width={7} />
</InlineField>
<InlineField>
<NumberInput min={0} value={options.substringEnd ?? 0} onChange={onSubstringEndChange} width={7} />
</InlineField>
</InlineFieldRow>
)}
</>
);
}
export const formatStringTransformerRegistryItem: TransformerRegistryItem<FormatStringTransformerOptions> = {
id: DataTransformerID.formatString,
editor: FormatStringTransfomerEditor,
transformation: standardTransformers.formatStringTransformer,
name: standardTransformers.formatStringTransformer.name,
state: PluginState.beta,
description: standardTransformers.formatStringTransformer.description,
categories: new Set([TransformerCategory.Reformat]),
};

View File

@ -1,4 +1,5 @@
import { TransformerRegistryItem } from '@grafana/data';
import { config } from '@grafana/runtime';
import { filterByValueTransformRegistryItem } from './FilterByValueTransformer/FilterByValueTransformerEditor';
import { heatmapTransformRegistryItem } from './calculateHeatmap/HeatmapTransformerEditor';
@ -8,6 +9,7 @@ import { concatenateTransformRegistryItem } from './editors/ConcatenateTransform
import { convertFieldTypeTransformRegistryItem } from './editors/ConvertFieldTypeTransformerEditor';
import { filterFieldsByNameTransformRegistryItem } from './editors/FilterByNameTransformerEditor';
import { filterFramesByRefIdTransformRegistryItem } from './editors/FilterByRefIdTransformerEditor';
import { formatStringTransformerRegistryItem } from './editors/FormatStringTransformerEditor';
import { formatTimeTransformerRegistryItem } from './editors/FormatTimeTransformerEditor';
import { groupByTransformRegistryItem } from './editors/GroupByTransformerEditor';
import { groupingToMatrixTransformRegistryItem } from './editors/GroupingToMatrixTransformerEditor';
@ -59,6 +61,7 @@ export const getStandardTransformers = (): Array<TransformerRegistryItem<any>> =
limitTransformRegistryItem,
joinByLabelsTransformRegistryItem,
partitionByValuesTransformRegistryItem,
...(config.featureToggles.formatString ? [formatStringTransformerRegistryItem] : []),
formatTimeTransformerRegistryItem,
timeSeriesTableTransformRegistryItem,
];

View File

@ -0,0 +1,25 @@
<svg width="58" height="48" viewBox="0 0 58 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1.04083V13H15V0H0.626828C0.460583 0 0.301147 0.109659 0.183594 0.304853C0.0660407 0.500047 0 0.764787 0 1.04083Z" fill="url(#paint0_linear_1810_47693)"/>
<path d="M15 17.5H0V30.5H15V17.5Z" fill="#84AFF1"/>
<path d="M15 35H0V48H15V35Z" fill="#84AFF1"/>
<path d="M9.65 9.3H9.3V3.35C9.3 3.25717 9.26313 3.16815 9.19749 3.10251C9.13185 3.03687 9.04283 3 8.95 3H7.725C7.66392 3.00037 7.604 3.01672 7.5512 3.04742C7.49839 3.07812 7.45454 3.1221 7.424 3.175L3.8505 9.3H3.35C3.25717 9.3 3.16815 9.33687 3.10251 9.40251C3.03687 9.46815 3 9.55717 3 9.65C3 9.74283 3.03687 9.83185 3.10251 9.89749C3.16815 9.96313 3.25717 10 3.35 10H4.75C4.84283 10 4.93185 9.96313 4.99749 9.89749C5.06313 9.83185 5.1 9.74283 5.1 9.65C5.1 9.55717 5.06313 9.46815 4.99749 9.40251C4.93185 9.33687 4.84283 9.3 4.75 9.3H4.659L5.884 7.2H8.6V9.3H8.25C8.15717 9.3 8.06815 9.33687 8.00251 9.40251C7.93687 9.46815 7.9 9.55717 7.9 9.65C7.9 9.74283 7.93687 9.83185 8.00251 9.89749C8.06815 9.96313 8.15717 10 8.25 10H9.65C9.74283 10 9.83185 9.96313 9.89749 9.89749C9.96313 9.83185 10 9.74283 10 9.65C10 9.55717 9.96313 9.46815 9.89749 9.40251C9.83185 9.33687 9.74283 9.3 9.65 9.3ZM8.6 6.5H6.2935L7.9245 3.7H8.6V6.5Z" fill="white"/>
<path d="M6.84608 27C7.52244 27 7.9902 26.7377 8.25569 26.2415H8.29362V26.9178H9.74747V23.6182C9.74747 22.591 8.83407 22 7.59829 22C6.29298 22 5.55341 22.6574 5.43015 23.5424L6.85556 23.5929C6.92193 23.2832 7.17794 23.0936 7.58565 23.0936C7.96492 23.0936 8.20512 23.2769 8.20512 23.6024V23.6182C8.20512 23.9153 7.88274 23.9785 7.05468 24.0512C6.07174 24.1334 5.25 24.4968 5.25 25.5714C5.25 26.5322 5.91688 27 6.84608 27ZM7.32333 25.9886C6.96618 25.9886 6.71334 25.818 6.71334 25.4956C6.71334 25.1827 6.95986 24.993 7.39918 24.9267C7.68679 24.8856 8.04077 24.8224 8.2146 24.7307V25.1922C8.2146 25.6662 7.81637 25.9886 7.32333 25.9886Z" fill="#24292E"/>
<path d="M5 44.4362H6.56634V43.7145H6.61489C6.81553 44.1261 7.26214 44.5 8.01294 44.5C9.11327 44.5 10 43.729 10 42.213C10 40.642 9.06149 39.9261 8.02265 39.9261C7.23625 39.9261 6.80583 40.3377 6.61489 40.7464H6.58252V38.5H5V44.4362ZM6.55016 42.2101C6.55016 41.4826 6.88673 41.0333 7.46602 41.0333C8.05178 41.0333 8.3754 41.4942 8.3754 42.2101C8.3754 42.929 8.05178 43.3957 7.46602 43.3957C6.88673 43.3957 6.55016 42.9319 6.55016 42.2101Z" fill="#24292E"/>
<path d="M28.9067 30C29.6011 30 34.9327 26 34.9327 24C34.9327 22 29.7357 18 28.9067 18C28.0777 18 27.4023 18.5 27.4023 19.4756C27.4023 20.4512 30.9067 22.9206 30.9067 22.9206C30.9067 22.9206 23.2539 22.25 23 22.9206C22.7461 23.5911 22.7461 24.4089 23 25.0794C23.2539 25.75 30.9067 25.0794 30.9067 25.0794C30.9067 25.0794 27.4023 27.75 27.4023 28.5301C27.4023 29.3103 28.2123 30 28.9067 30Z" fill="#CCCCDC"/>
<path d="M43 1.04083V13H58V0H43.6268C43.4606 0 43.3011 0.109659 43.1836 0.304853C43.066 0.500047 43 0.764787 43 1.04083Z" fill="url(#paint1_linear_1810_47693)"/>
<path d="M58 17.5H43V30.5H58V17.5Z" fill="#84AFF1"/>
<path d="M58 35H43V48H58V35Z" fill="#84AFF1"/>
<path d="M52.65 9.3H52.3V3.35C52.3 3.25717 52.2631 3.16815 52.1975 3.10251C52.1318 3.03687 52.0428 3 51.95 3H50.725C50.6639 3.00037 50.604 3.01672 50.5512 3.04742C50.4984 3.07812 50.4545 3.1221 50.424 3.175L46.8505 9.3H46.35C46.2572 9.3 46.1682 9.33687 46.1025 9.40251C46.0369 9.46815 46 9.55717 46 9.65C46 9.74283 46.0369 9.83185 46.1025 9.89749C46.1682 9.96313 46.2572 10 46.35 10H47.75C47.8428 10 47.9318 9.96313 47.9975 9.89749C48.0631 9.83185 48.1 9.74283 48.1 9.65C48.1 9.55717 48.0631 9.46815 47.9975 9.40251C47.9318 9.33687 47.8428 9.3 47.75 9.3H47.659L48.884 7.2H51.6V9.3H51.25C51.1572 9.3 51.0682 9.33687 51.0025 9.40251C50.9369 9.46815 50.9 9.55717 50.9 9.65C50.9 9.74283 50.9369 9.83185 51.0025 9.89749C51.0682 9.96313 51.1572 10 51.25 10H52.65C52.7428 10 52.8318 9.96313 52.8975 9.89749C52.9631 9.83185 53 9.74283 53 9.65C53 9.55717 52.9631 9.46815 52.8975 9.40251C52.8318 9.33687 52.7428 9.3 52.65 9.3ZM51.6 6.5H49.2935L50.9245 3.7H51.6V6.5Z" fill="white"/>
<path d="M49.0114 26.8182H47.5L49.4631 21H51.3352L53.2983 26.8182H51.7869L51.4034 25.5966H49.3949L49.0114 26.8182ZM49.7301 24.5284H51.0682L50.4205 22.4659H50.375L49.7301 24.5284Z" fill="black"/>
<path d="M48.5 44.8182V39H50.9261C52.233 39 52.9091 39.5938 52.9091 40.4716C52.9091 41.1364 52.4602 41.5994 51.821 41.733V41.7898C52.5256 41.821 53.1364 42.3494 53.1364 43.1903C53.1364 44.1307 52.3977 44.8182 51.1108 44.8182H48.5ZM49.9062 43.6847H50.7756C51.3835 43.6847 51.6676 43.4403 51.6676 43.0284C51.6676 42.5824 51.3381 42.2983 50.8011 42.2983H49.9062V43.6847ZM49.9062 41.392H50.6847C51.1449 41.392 51.4744 41.1534 51.4744 40.7443C51.4744 40.358 51.1761 40.1165 50.7074 40.1165H49.9062V41.392Z" fill="black"/>
<defs>
<linearGradient id="paint0_linear_1810_47693" x1="0" y1="6.5052" x2="15" y2="6.5052" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2CC0C"/>
<stop offset="1" stop-color="#FF9830"/>
</linearGradient>
<linearGradient id="paint1_linear_1810_47693" x1="43" y1="6.5052" x2="58" y2="6.5052" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2CC0C"/>
<stop offset="1" stop-color="#FF9830"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,25 @@
<svg width="58" height="48" viewBox="0 0 58 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1.04083V13H15V0H0.626828C0.460583 0 0.301147 0.109659 0.183594 0.304853C0.0660407 0.500047 0 0.764787 0 1.04083Z" fill="url(#paint0_linear_1811_57031)"/>
<path d="M15 17.5H0V30.5H15V17.5Z" fill="#84AFF1"/>
<path d="M15 35H0V48H15V35Z" fill="#84AFF1"/>
<path d="M9.65 9.3H9.3V3.35C9.3 3.25717 9.26313 3.16815 9.19749 3.10251C9.13185 3.03687 9.04283 3 8.95 3H7.725C7.66392 3.00037 7.604 3.01672 7.5512 3.04742C7.49839 3.07812 7.45454 3.1221 7.424 3.175L3.8505 9.3H3.35C3.25717 9.3 3.16815 9.33687 3.10251 9.40251C3.03687 9.46815 3 9.55717 3 9.65C3 9.74283 3.03687 9.83185 3.10251 9.89749C3.16815 9.96313 3.25717 10 3.35 10H4.75C4.84283 10 4.93185 9.96313 4.99749 9.89749C5.06313 9.83185 5.1 9.74283 5.1 9.65C5.1 9.55717 5.06313 9.46815 4.99749 9.40251C4.93185 9.33687 4.84283 9.3 4.75 9.3H4.659L5.884 7.2H8.6V9.3H8.25C8.15717 9.3 8.06815 9.33687 8.00251 9.40251C7.93687 9.46815 7.9 9.55717 7.9 9.65C7.9 9.74283 7.93687 9.83185 8.00251 9.89749C8.06815 9.96313 8.15717 10 8.25 10H9.65C9.74283 10 9.83185 9.96313 9.89749 9.89749C9.96313 9.83185 10 9.74283 10 9.65C10 9.55717 9.96313 9.46815 9.89749 9.40251C9.83185 9.33687 9.74283 9.3 9.65 9.3ZM8.6 6.5H6.2935L7.9245 3.7H8.6V6.5Z" fill="white"/>
<path d="M6.84608 27C7.52244 27 7.9902 26.7377 8.25569 26.2415H8.29362V26.9178H9.74747V23.6182C9.74747 22.591 8.83407 22 7.59829 22C6.29298 22 5.55341 22.6574 5.43015 23.5424L6.85556 23.5929C6.92193 23.2832 7.17794 23.0936 7.58565 23.0936C7.96492 23.0936 8.20512 23.2769 8.20512 23.6024V23.6182C8.20512 23.9153 7.88274 23.9785 7.05468 24.0512C6.07174 24.1334 5.25 24.4968 5.25 25.5714C5.25 26.5322 5.91688 27 6.84608 27ZM7.32333 25.9886C6.96618 25.9886 6.71334 25.818 6.71334 25.4956C6.71334 25.1827 6.95986 24.993 7.39918 24.9267C7.68679 24.8856 8.04077 24.8224 8.2146 24.7307V25.1922C8.2146 25.6662 7.81637 25.9886 7.32333 25.9886Z" fill="#24292E"/>
<path d="M5 44.4362H6.56634V43.7145H6.61489C6.81553 44.1261 7.26214 44.5 8.01294 44.5C9.11327 44.5 10 43.729 10 42.213C10 40.642 9.06149 39.9261 8.02265 39.9261C7.23625 39.9261 6.80583 40.3377 6.61489 40.7464H6.58252V38.5H5V44.4362ZM6.55016 42.2101C6.55016 41.4826 6.88673 41.0333 7.46602 41.0333C8.05178 41.0333 8.3754 41.4942 8.3754 42.2101C8.3754 42.929 8.05178 43.3957 7.46602 43.3957C6.88673 43.3957 6.55016 42.9319 6.55016 42.2101Z" fill="#24292E"/>
<path d="M28.9067 30C29.6011 30 34.9327 26 34.9327 24C34.9327 22 29.7357 18 28.9067 18C28.0778 18 27.4023 18.5 27.4023 19.4756C27.4023 20.4512 30.9067 22.9206 30.9067 22.9206C30.9067 22.9206 23.2539 22.25 23 22.9206C22.7461 23.5911 22.7461 24.4089 23 25.0794C23.2539 25.75 30.9067 25.0794 30.9067 25.0794C30.9067 25.0794 27.4023 27.75 27.4023 28.5301C27.4023 29.3103 28.2124 30 28.9067 30Z" fill="#24292E"/>
<path d="M43 1.04083V13H58V0H43.6268C43.4606 0 43.3011 0.109659 43.1836 0.304853C43.066 0.500047 43 0.764787 43 1.04083Z" fill="url(#paint1_linear_1811_57031)"/>
<path d="M58 17.5H43V30.5H58V17.5Z" fill="#84AFF1"/>
<path d="M58 35H43V48H58V35Z" fill="#84AFF1"/>
<path d="M52.65 9.3H52.3V3.35C52.3 3.25717 52.2631 3.16815 52.1975 3.10251C52.1318 3.03687 52.0428 3 51.95 3H50.725C50.6639 3.00037 50.604 3.01672 50.5512 3.04742C50.4984 3.07812 50.4545 3.1221 50.424 3.175L46.8505 9.3H46.35C46.2572 9.3 46.1682 9.33687 46.1025 9.40251C46.0369 9.46815 46 9.55717 46 9.65C46 9.74283 46.0369 9.83185 46.1025 9.89749C46.1682 9.96313 46.2572 10 46.35 10H47.75C47.8428 10 47.9318 9.96313 47.9975 9.89749C48.0631 9.83185 48.1 9.74283 48.1 9.65C48.1 9.55717 48.0631 9.46815 47.9975 9.40251C47.9318 9.33687 47.8428 9.3 47.75 9.3H47.659L48.884 7.2H51.6V9.3H51.25C51.1572 9.3 51.0682 9.33687 51.0025 9.40251C50.9369 9.46815 50.9 9.55717 50.9 9.65C50.9 9.74283 50.9369 9.83185 51.0025 9.89749C51.0682 9.96313 51.1572 10 51.25 10H52.65C52.7428 10 52.8318 9.96313 52.8975 9.89749C52.9631 9.83185 53 9.74283 53 9.65C53 9.55717 52.9631 9.46815 52.8975 9.40251C52.8318 9.33687 52.7428 9.3 52.65 9.3ZM51.6 6.5H49.2935L50.9245 3.7H51.6V6.5Z" fill="white"/>
<path d="M49.0114 26.8182H47.5L49.4631 21H51.3352L53.2983 26.8182H51.7869L51.4034 25.5966H49.3949L49.0114 26.8182ZM49.7301 24.5284H51.0682L50.4205 22.4659H50.375L49.7301 24.5284Z" fill="black"/>
<path d="M48.5 44.8182V39H50.9261C52.233 39 52.9091 39.5938 52.9091 40.4716C52.9091 41.1364 52.4602 41.5994 51.821 41.733V41.7898C52.5256 41.821 53.1364 42.3494 53.1364 43.1903C53.1364 44.1307 52.3977 44.8182 51.1108 44.8182H48.5ZM49.9062 43.6847H50.7756C51.3835 43.6847 51.6676 43.4403 51.6676 43.0284C51.6676 42.5824 51.3381 42.2983 50.8011 42.2983H49.9062V43.6847ZM49.9062 41.392H50.6847C51.1449 41.392 51.4744 41.1534 51.4744 40.7443C51.4744 40.358 51.1761 40.1165 50.7074 40.1165H49.9062V41.392Z" fill="black"/>
<defs>
<linearGradient id="paint0_linear_1811_57031" x1="0" y1="6.5052" x2="15" y2="6.5052" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2CC0C"/>
<stop offset="1" stop-color="#FF9830"/>
</linearGradient>
<linearGradient id="paint1_linear_1811_57031" x1="43" y1="6.5052" x2="58" y2="6.5052" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2CC0C"/>
<stop offset="1" stop-color="#FF9830"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB