Logstash: configuring aggregate + elapsed filters - elasticsearch

I have these logs:
"03.08.2020 10:56:38","Event LClick","Type Menu","t=0","beg"
"03.08.2020 10:56:38","Event LClick","Type Menu","Detail SomeDetail","t=109","end"
"03.08.2020 10:56:40","Event LClick","t=1981","beg"
"03.08.2020 10:56:40","Event LClick","t=2090","end"
"03.08.2020 10:56:41","Event LClick","Type ToolBar","t=3026","beg"
"03.08.2020 10:56:43","Event LClick","Type ToolBar","Detail User_Desktop","t=4477","end"
"03.08.2020 10:56:44","Event FormActivate","Name Form_Name:IsaA","t=5444"
"03.08.2020 10:56:51","Event LClick","t=12543","beg"
"03.08.2020 10:56:51","Event LClick","t=12605","end"
"03.08.2020 10:56:52","Event LClick","Form ","Type Label","Name Application.for.training","t=13853","beg"
"03.08.2020 10:57:54","Event LClick","Form Application.for.training","Type Label","Name Application.for.training","t=75442","end"
"03.08.2020 10:57:54","Event FormActivate","Name List.form","t=75785"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85769","beg"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85769","end"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85847","beg"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85847","end"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85879","beg"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85879","end"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85925","beg"
"03.08.2020 10:58:04","Event Wheel","Form List.form","Type FormTable","Name Список","t=85925","end"
"03.08.2020 10:58:08","Event LClick","Form List.form","Type FormTable","Name Список","t=89373","beg"
"03.08.2020 10:58:08","Event LClick","Form List.form","Type FormTable","Name Список","Detail Data","t=89451","end"
"03.08.2020 10:58:15","Event LClick","Form List.form","Type FormTable","Name Список","t=96580","beg"
"03.08.2020 10:58:15","Event LClick","Form List.form","Type FormTable","Name Список","Detail Data","t=96643","end"
"03.08.2020 10:58:15","Event LBtnDbl","Form List.form","Type FormTable","Name Список","t=96752","beg"
"03.08.2020 10:59:22","Event FormActivate","Name Another.Form","t=164004"
"03.08.2020 10:59:22","Event LBtnDbl","Form Another.Form","Type FormTable","Name Список","Detail Data","t=164004","end"
"03.08.2020 10:59:25","Event LClick","Form Another.Form","Type ToolBar","Name КоманднаяПанельПереченьРеквизитов","t=167171","beg"
"03.08.2020 10:59:26","Event LClick","Form Another.Form","Type ToolBar","Name КоманднаяПанельПереченьРеквизитов","Detail Заполнить","t=167249","end"
...
My logstash config:
input {
beats {
port => '5044'
}
}
filter {
grok {
patterns_dir => ['./patterns']
match => { 'message' => '%{TIME:timestamp}(","Event\s)(?<Event>([^"]+))(","Form\s)?(?<Form>([^"]+))?(","ParentType\s)?(?<parent_type>([^"]+))?(","ParentName\s)?(?<parent_name>([^"]+))?(","Type\s)?(?<type>([^"]+))?(","Name\s)?(?<Name_of_form>([^"]+))?(","Detail\s)?(?<Detail>([^"]+))?(","t=)?(?<t>([\d]+))?(",")?(?<Status>(end|beg))?' }
add_tag => [ '%{Status}' ]
}
dissect {
mapping => {
'[log][file][path]' => 'C:\Program Files\Filebeat\logs\%{somethingtoo}\%{something}\%{user}\%{filename}.txt'
}
}
date {
match => [ 'timestamp', 'dd.MM.yyyy HH:mm:ss' ]
}
elapsed {
unique_id_field => 'Event'
start_tag => 'beg'
end_tag => 'end'
new_event_on_match => false
}
if 'elapsed' in [tags] {
aggregate {
task_id => '%{Event}'
code => 'map["duration"] = [(event.get("elapsed_time")*1000).to_i]'
map_action => 'create'
}
}
mutate {
remove_field => ['timestamp', 'ecs', 'log', 'tags', 'message', '#version', 'something', 'somethingtoo', 'filename', 'input', 'host', 'agent', 't', 'parent_type', 'parent_name', 'type']
rename => {'elapsed_time' => 'Event_duration'}
}
}
output {
elasticsearch {
hosts => ['localhost:9200']
index => 'test'
}
}
Question: now I calculate the time difference between the lines using beg (the beginning of an action) and end (the end of an action). But that doesn't make much sense because it's almost always 0 seconds. How would I implement it like this: when a form field appears in the line (if 'Form' in message), consider the difference between the first appearance of a certain form and the last appearance. How can I implement this if it is not clear what to bind the end_tag to.
For the logs above, I should get the following:
Some form is activated (Event FormActivate), in a separate field form name (Name Name_of_form). Then the actions of this form follow (the time of which must be counted) and then the activation of the new form and so on until the end of the file.
As long as the form is the same. Move on.
The last appearance of this form in a row
Elapsed_time: "08/03/2020 10:58:15" - "08/03/2020 10:58:04" = 11 seconds
I would be very grateful for any help!

Related

Id value on q-td for Quasar qTable when displaying complex content in table cell

I'm retrieving data from a Laravel API and formatting it into a Quasar Table. I have it mostly working but having an issue with the key attribute on the q-td tag. For a simple key value pair, I can use the key name, but in some columns, I have nested data or an array - for example - the status is an object with name and color and which I use to populate a qSelect component. The tags field contains an array of tags which I used in a v-for to display a series of qBadges.
If I specify the field name I get the error 'Avoid using non-primitive value as key, use string/number value instead.' - because the value of that field is an object or array and not a string or number. I tried using id or any other field I get the error 'Duplicate keys detected: 'id'. This may cause an update error.'
<q-table
ref="table"
title="Invoices"
:data="invoices"
:columns="columns"
color="primary"
row-key="id"
:loading="loading"
no-data-label="no invoices within search prameters"
:visible-columns="visibleColumns"
:selected-rows-label="getSelectedString"
selection="multiple"
:selected.sync="selected">
<template v-slot:top="props">
<q-btn
flat round dense
:icon="props.inFullscreen ? 'fullscreen_exit' : 'fullscreen'"
#click="props.toggleFullscreen"
class="q-ml-md"
/>
<q-space />
<q-select
v-model="visibleColumns"
multiple
borderless
dense
options-dense
emit-value
map-options
:options="columns"
option-value="name"
style="min-width: 150px"
/>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-checkbox dense v-model="props.selected" />
</q-td>
<q-td key="invoice_number" :props="props">
{{ props.row.invoice_number }}
</q-td>
<q-td key="id" :props="props">
{{ props.row.business.name }}
</q-td>
<q-td key="invoiced_at" :props="props">
<span v-if="props.row.invoiced_at">
{{ formatDate(props.row.invoiced_at, 'MMM D, YYYY')}}
</span>
</q-td>
<q-td key="paid_at" :props="props">
<span v-if="props.row.paid_at">
{{ formatDate(props.row.paid_at, 'MMM D, YYYY')}}
</span>
</q-td>
<q-td key="id" :props="props">
<q-select v-if="authuser && authuser.is_crew" rounded outlined fill-input :label-color="props.row.status.color" :color="props.row.status.color" v-model="props.row.status_id" :key="props.row.id" :options="statuses" label="Status" emit-value map-options option-label="name" #input="(val) => saveRow(props.row)">
<template v-slot:append>
<q-icon name="fas fa-circle" :color="props.row.status.color" />
</template>
</q-select>
<q-badge v-if="!authuser || !authuser.is_crew && props.row.status" :color="props.row.status.color">{{props.row.status.name}}</q-badge>
</q-td>
<q-td key="notes" :props="props">
<div class="table-description cursor-pointer">
<q-icon v-if="!props.row.notes" name="fas fa-comment-alt" class="float-right" />
{{ props.row.notes }}
<q-popup-edit
v-if="authuser.is_crew"
buttons
v-model="props.row.notes"
#save="(val, initialValue) => saveRow(props.row)"
>
<q-input
type="textarea"
v-model="props.row.notes"
autofocus
counter
#keyup.enter.stop
/>
</q-popup-edit>
</div>
</q-td>
<q-td key="id" :props="props">
<q-chip size="xs" v-for="(tag, idx) in props.row.tags" :key="idx" :label="tag" />
</q-td>
<q-td key="total" :props="props">
{{ props.row.total }}
</q-td>
<q-td key="id" :props="props">
<div class="text-grey-8 q-gutter-xs">
<q-btn size="12px" flat dense round icon="more_vert">
<q-menu>
<q-list style="min-width: 100px">
<q-item v-if="mode !== 'view'" clickable v-close-popup :to="'/invoice/'+props.row.id+'/view'">
<q-item-section avatar>
<q-icon color="primary" name="fas fa-eye" />
</q-item-section>
<q-item-section>view</q-item-section>
</q-item>
<q-item v-if="authuser.is_crew" clickable v-close-popup :to="'/invoice/'+props.row.id+'/edit'">
<q-item-section avatar>
<q-icon color="primary" name="fas fa-edit" />
</q-item-section>
<q-item-section>edit</q-item-section>
</q-item>
<q-item v-if="props.row.business" clickable v-close-popup :to="'/business/'+props.row.business.id">
<q-item-section avatar>
<q-icon color="primary" name="fas fa-user" />
</q-item-section>
<q-item-section>business</q-item-section>
</q-item>
<q-item v-if="authuser.is_crew" clickable v-close-popup #click.native="confirmDelete(props.row)">
<q-item-section avatar>
<q-icon color="negative" name="fas fa-trash" />
</q-item-section>
<q-item-section>delete</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</q-td>
</q-tr>
</template>
</q-table>
My columns:
columns: [
{ name: 'invoice_number', align: 'left', label: 'Inv Number', field: 'invoice_number', sortable: true },
{ name: 'vendor', align: 'left', label: 'Vendor', field: row => row.businessname, format: (val, row) => `${val}`, sortable: true },
{ name: 'invoiced_at', align: 'left', label: 'Invoiced', field: 'invoiced_at', sortable: true },
{ name: 'paid_at', align: 'left', label: 'Paid', field: 'paid_at', sortable: true },
{ name: 'status', align: 'left', label: 'Status', field: row => row.status.name, format: val => `${val}`, sortable: true },
{ name: 'notes', align: 'left', label: 'Notes', field: 'notes', sortable: false },
{ name: 'tags', align: 'center', label: 'Tags', field: 'tags', sortable: true },
{ name: 'total', align: 'right', label: 'Total', field: 'total', sortable: true },
{ name: 'id', align: 'right', label: 'Actions', field: 'id', sortable: false }
]
The data is returned from a Laravel Resource, here's a sample:
[{"id":49,"total":"19279.36","invoice_number":"2546","name":"Gillen Invoice Summer 2020 2546","notes":null,"status":{"id":20,"name":"Open","path":"invoice-open","priority":2,"color":"cyan-10","model":"App\Invoice","description":"Received, Unpaid Invoice","created_at":"2020-05-19T17:24:48.000000Z","updated_at":"2020-05-19T17:24:48.000000Z","deleted_at":null,"vessel_id":null},"status_id":20,"business":{"id":21,"name":"Gillen Diesel & Marine Services","phone":"(954) 927-6500","address1":"811 NE 3rd St","address2":null,"city":"Dania Beach","state":"FL","zipcode":"33004","address_country":null,"email":"davegillensr#gillenyacht.com","type":{"id":30,"name":"Repair","model":"App\Business","icon":null,"color":"grey","vessel_id":null,"equipment":[],"created_at":"2018-06-01T03:12:43.000000Z","updated_at":"2018-06-01T03:12:43.000000Z"},"type_id":30,"url":null
Thanks for any help - so excited to see more Quasar activity on the stack!
You have used id as key multiple times. Don't use id key use the name of the column as a key.
<q-td key="vendor" :props="props">
{{ props.row.business.name }}
</q-td>
<q-td key="tags" :props="props">
<q-chip size="xs" v-for="(tag, idx) in props.row.tags" :key="idx" :label="tag" />
</q-td>
You can use tags and Vendor as a key.
Example - https://codepen.io/Pratik__007/pen/PoNpyZV

Vee Validate - Sever side validation and front end validation

I have a laravel lumen endpoint which i return errors from. The issue i am facing is that vee validate is not setting the errors?
<template>
<v-container fluid class="ma-0 pa-0">
<v-card route>
<v-card-title class="primary white--text">Create New User</v-card-title>
<v-row align="center" :class="{'px-6': $vuetify.breakpoint.xs, 'pa-8': $vuetify.breakpoint.smAndUp}">
<v-row justify="space-between" v-if="!isCreating">
<v-col cols="12" md="4">
<v-row align="center" justify="center" class="mb-4">
<v-avatar color="primary" size="128">
<v-icon size="48" dark v-if="!userAvatarName()">mdi-account</v-icon>
<span
v-if="userAvatarName()"
class="white--text display-2"
>{{ userAvatarName() }}</span>
</v-avatar>
</v-row>
</v-col>
<v-col>
<ValidationObserver v-slot="{ invalid, validated, passes }" ref="provider">
<v-form #submit.prevent="passes(handleSubmit)">
<v-row >
<v-col class="py-0">
<VTextFieldWithValidation vid="username" rules="required" v-model="newUser['username']" label="Username" />
</v-col>
</v-row>
<v-row >
<v-col class="py-0">
<VTextFieldWithValidation vid="email" rules="required|email" v-model="newUser['email']" label="Email address" />
</v-col>
</v-row>
<v-row >
<v-col class="py-0">
<VTextFieldWithValidation rules="required" v-model="newUser['first_name']" label="First name" />
</v-col>
<v-col class="py-0">
<VTextFieldWithValidation rules="required" v-model="newUser['last_name']" label="Last name" />
</v-col>
</v-row>
<v-row >
<v-col class="py-0 mt-4">
<ValidationProvider name="role" rules="required" v-slot="{ errors, valid }">
<v-select
:error-messages="errors"
:success="valid"
:items="roles"
item-text="name" item-value="value"
label="Role"
outlined
v-model="newUser['role']"
></v-select>
</ValidationProvider>
</v-col>
</v-row>
<v-divider></v-divider>
<v-row >
<v-col class="py-0">
<v-switch
v-model="newUser['send_activation']"
label="Send Activation Email"
></v-switch>
</v-col>
</v-row>
<v-row v-if="!newUser['send_activation']">
<v-col class="py-0">
<ValidationProvider name="password" :rules="!newUser['send_activation'] ? 'required|min:8' : ''" v-slot="{ errors, valid }">
<v-text-field
outlined
counter
:error-messages="errors"
:success="valid"
label="Password"
:append-icon="userPasswordVisibility.password_current_show ? 'mdi-eye' : 'mdi-eye-off'"
:type="userPasswordVisibility.password_current_show ? 'text' : 'password'"
#click:append="userPasswordVisibility.password_current_show = !userPasswordVisibility.password_current_show"
v-model="newUser['password']"
></v-text-field>
</ValidationProvider>
</v-col>
<v-col class="py-0">
<ValidationProvider name="confirmation" :rules="!newUser['send_activation'] ? 'required|password:#password' : ''" v-slot="{ errors, valid }">
<v-text-field
outlined
counter
:error-messages="errors"
:success="valid"
label="Password Confirmation"
:append-icon="userPasswordVisibility.password_new_show ? 'mdi-eye' : 'mdi-eye-off'"
:type="userPasswordVisibility.password_new_show ? 'text' : 'password'"
#click:append="userPasswordVisibility.password_new_show = !userPasswordVisibility.password_new_show"
v-model="newUser['password_confirmation']"
></v-text-field>
</ValidationProvider>
</v-col>
</v-row>
<v-row class="mt-4">
<v-col class="py-0">
<v-btn text color="primary" :to="{ name: 'organisation/users', params: { org_id: organisation.id }}">Cancel</v-btn>
</v-col>
<v-col class="py-0 text-right">
<v-tooltip top>
<template v-slot:activator="{ on }">
<v-btn outlined color="primary" v-on="on" #click="passes(handleSubmit)" :disabled="invalid || !validated">Add New User</v-btn>
</template>
<span>Create new user and add them to <strong>{{organisation.name}}</strong>.</span>
</v-tooltip>
</v-col>
</v-row>
</v-form>
</ValidationObserver>
</v-col>
</v-row>
<v-row align="center" justify="center" v-if="isCreating">
<v-progress-circular
class="ma-5"
indeterminate
color="teal accent-1 darken-2" >
</v-progress-circular>
</v-row>
</v-row>
</v-card>
</v-container>
</template>
<script>
import { mapState } from 'vuex'
import { CREATE_USER } from '#/_apollo/GraphQL/userGraphQL'
import BreadcrumbsManager from '#/_util/breadcrumbManager'
import VTextFieldWithValidation from '#/components/inputs/VTextFieldWithValidation'
import { ValidationProvider, ValidationObserver } from "vee-validate"
export default {
name: 'CreateUser',
mixins: [BreadcrumbsManager],
props: [
'organisation'
],
components: {
ValidationObserver,
ValidationProvider,
VTextFieldWithValidation
},
data() {
return {
userPasswordVisibility: {
password_current_show: false,
password_new_show: false,
password_new_confirm_show: false
},
newUser: {
send_activation: 1
},
roles: [
{ name: 'Admin', value: 'organisation_admin' },
{ name: 'User', value: 'user' }
],
isCreating: false
}
},
computed: {
...mapState({
authUser: state => state.AUTH_STORE.authUser
}),
},
methods: {
userAvatarName() {
let name = '';
if (this.newUser['first_name']) {
name += `${this.newUser['first_name'].charAt(0).toUpperCase()}`
}
if (this.newUser['last_name']) {
name += `${this.newUser['last_name'].charAt(0).toUpperCase()}`
}
if (name !== '') {
return name
}
return false
},
async handleSubmit() {
this.isCreating = true
this.newUser['reseller_id'] = this.authUser.reseller_id
this.newUser['organisation_id'] = parseInt(this.$route.params.org_id)
this.newUser['send_activation'] = this.newUser['send_activation'] ? 1 : 0
var refs = this.$refs.provider
this.$apollo
.mutate({
mutation: CREATE_USER,
variables: this.newUser
})
.then(response => {
this.isCreating = false
const user = response.data.createUser
this.$store.commit('USER_STORE/CREATE_USER', response.data.createUser)
this.$toast.success('Successfully created new user.')
this.$router.push({ name: "organisation/users", params: { org_id: this.$route.params.org_id }}, () => {})
}).catch((error) => {
this.isCreating = false
this.$toast.error(error.graphQLErrors[0].extensions.code.message)
let errors = error.graphQLErrors[0].extensions.code.errors
console.log(errors)
refs.setErrors(errors)
console.log(refs)
})
refs.validate();
}
},
created() {
this.setBreadcrumbs([
{ text: 'Dashboard' , path: '/' },
{ text: 'Organisations' , path: '/organisation/all/' },
{ text: ':organisation' },
{ text: 'Users', path: `/organisation/${this.organisation.org_id}/users/` },
{ text: 'Create' }
])
this.replaceBreadcrumb({
find: ':organisation',
replace: { text: this.organisation.name, path: `/organisation/${this.organisation.org_id}` }
})
}
}
</script>
In the catch error on handle submit i can see the sever errors but my view wont show an error on the field?
I have took inspiration from the following from the docs:
https://codesandbox.io/s/veevalidate-backend-driven-validation-ynrp9?from-embed=&file=/src/App.vue
But to no avail.
Can anyone help?
It turns out apollo client is a promise which isnt resolved so the fix is:
const userResponse = await this.$apollo
.mutate({
mutation: CREATE_USER,
variables: this.newUser
})
.then(response => {
this.isCreating = false
const user = response.data.createUser
this.$store.commit('USER_STORE/CREATE_USER', response.data.createUser)
this.$toast.success('Successfully created new user.')
this.$router.push({ name: "organisation/users", params: { org_id: this.$route.params.org_id }}, () => {})
}).catch((error) => {
this.isCreating = false
this.$toast.error(error.graphQLErrors[0].extensions.code.message)
return error.graphQLErrors[0].extensions.code.errors.detail
})
this.$refs.provider.setErrors(userResponse);

defaultValue in AOR-dependent-input

I want to set defaultValue for field in aor-dependent-input,
I already set defaultValue for ReferenceInput, but how can I set defaultValue for DependentInput using SubGenreInput
Here is my code:
<Create {...propsEdit} title=" " actions="">
<SimpleForm redirect={"/caregiver/" + cid}>
{/*<ReferenceArrayInput>*/}
<DisabledInput source="user" defaultValue={cid} label="Caregiver ID"/>
<ReferenceInput label="Centre" source="centre" defaultValue={this.props.record.student.centre.id} reference="centre" sort={{ field: 'name', order: 'ASC' }} allowEmpty>
<SelectInput optionText="name" />
</ReferenceInput>
<DependentInput dependsOn="centre">
<SubGenreInput source="current_group" optionText="label" optionValue="id" type="classgroup"/>
</DependentInput>
<DependentInput dependsOn="current_group" defaultValue={this.props.record.student.id}>
<SubGenreInput source="student_id" optionText="fullname" optionValue="id" type="student"/>
</DependentInput>
{/*</ReferenceArrayInput>*/}
<RadioButtonGroupInput source="account_type" defaultValue={10} choices={[
{ id: 10, name: 'Caregiver' },
{ id: 20, name: 'Guardian' },
]} optionText="name" optionValue="id" />
<TextInput source="relationship" label="Relationship"/>
</SimpleForm>
Interface:
https://i.stack.imgur.com/wFHKC.png
// I'm not allow to post the images :(
This is a known issue which will be addressed by version 1.3.0 of admin-on-rest. In the mean time you can use this workaround:
<DependentInput source="appointmentTimeslot" dependsOn="planned" defaultValue={1}>
<ReferenceInput source="appointmentTimeslot" reference="timeslots" allowEmpty={true} >
<SelectInput optionText="name" optionValue="id" />
</ReferenceInput>
</DependentInput>
Note that we specified source on both the DependentInput component and its child. The defaultValue, however, is specified on the DependentInput component only.
This workaround won't work if you pass multiple children to the DependentInput component.

How can I create a SaveButton which returns to me to a special URL

I know that I can use an url as redirect parameter for an SaveButton like this:
const ResourcePropertyEditToolbar = props => <Toolbar {...props} >
<SaveButton />
<SaveButton label="save and resource" redirect={"/resource/21"}
submitOnEnter={false} raised={false} />
</Toolbar>;
As you see the "/resource/21" is hard coded and it works (for this entry ;-) How can I dynamically create the url? The value itself is included in the current data set which is edited, like this:
export const ResourcePropertyEdit = (props) => (
<Edit {...props} >
<SimpleForm toolbar={<ResourcePropertyEditToolbar />}>
<NumberInput source="position" defaultValue="1"/>
<TextInput source="property_name" validate={required}/>
<ReferenceInput label="Resource" source="fk_resource"
reference="resource">
<AutocompleteInput optionText="shortname"/>
</ReferenceInput>
</SimpleForm>
</Edit>
);
It is the value of the selected fk_resource.
Here is the code snippet for the base Form with the ReferenceManyField Entry, the target is to jump back to this view after editing a resource_property.
export const ResourceEdit = (props) => (
<Edit {...props}>
<SimpleForm>
<TextInput source="shortname" validate={required}/>
<ReferenceManyField target="fk_resource"
reference="resource_property" >
<Datagrid>
<NumberField source="position"/>
<TextField source="property_name"/>
<ReferenceField label="Property" source="fk_property" reference="property" allowEmpty>
<TextField source="shortname"/>
</ReferenceField>
<EditButton/>
</Datagrid>
</ReferenceManyField>
</SimpleForm>
</Edit>
);
Thanks for any help
The current data set is called the record in AOR. If the URL is in the record you can access it simply by
const ResourcePropertyEditToolbar = props => <Toolbar {...props} >
<SaveButton />
<SaveButton label="save and resource" redirect={props.record.url ? props.record.url : null}
submitOnEnter={false} raised={false} />
</Toolbar>

Sort item numbers with dot on array collection in flex

I need to order an array of items by their item numbers on a datagrid in Flex 3.5.
I don't actually need to re-order it once it's inside the datagrid, I just need it to be sorted already on the arraycollection before sending it to the dataprovider.
My problem is that the proprety 'item_number' that I need to be sorted it's a string and it's built with the lot number and a dot, like this:
1.1,
1.2,
1.3,
2.1,
2.2,
3.1,
3.2,
3.3,
3.4,
3.5,
3.6,
3.7,
3.8,
3.9,
3.10,
3.11
I need it to be ordered like this.
If I try to just order them by number, 3.2 is bigger than 3.11, so it wouldn't work. I need to order them first by the integer before the dot and only after it, by the integer after the dot, before moving on to the next integer before the dot
Also I have another problem. The item_number attribute is inside an object that is inside another object on my arraycollection.
To get to it I have to:
array_collection.item.item_number
So to sum it up, I need to list an array ordered by an attribute that's inside another object of the arrayitem and it's a number on a string separated by dot.
This is a simplified version of my code:
<mx:Script>
<![CDATA[
public function print_data_grid(array_collection):void
{
my_data_grid.dataProvider = array_collection
}
]]>
</mx:Script>
<mx:DataGrid id="my_data_grid">
<mx:columns>
<mx:DataGridColumn headerText="# Item">
<mx:itemRenderer>
<mx:Component>
<mx:Label toolTip="{this.text}" text="{data.product.item_number}"/>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn headerText="Item Name">
<mx:itemRenderer>
<mx:Component>
<mx:Label toolTip="{this.text}" text="{data.product.name}"/>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
You can write a custom sort function. Here is an example. Please check if it fulfills your requirement.
<?xml version="1.0"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.utils.ObjectUtil;
private var myArrayCollection:ArrayCollection = new ArrayCollection([
{product: {item_number: "1.1", name: "A"}},
{product: {item_number: "10.2", name: "Lottery"}},
{product: {item_number: "10.11", name: "Book"}},
{product: {item_number: "1.13", name: "DVD"}},
{product: {item_number: "1.221", name: "Car"}},
{product: {item_number: "1.211", name: "Mobile"}},
{product: {item_number: "10.1111", name: "Laptop"}},
{product: {item_number: "11.1", name: "Camera"}},
{product: {item_number: "12.1", name: "Desktop"}},
{product: {item_number: "144.41", name: "iPad"}},
{product: {item_number: "14.21", name: "Tablet"}},
{product: {item_number: "14.111", name: "Android phone"}},
{product: {item_number: "10.1", name: "TV"}},
{product: {item_number: "10.100", name: "Bulb"}}]);
private function createCopyOfArrayCollection():ArrayCollection
{
var copyOfArrayCollection:ArrayCollection = new ArrayCollection();
for(var i = 0; i < myArrayCollection.length; i++)
{
copyOfArrayCollection.addItem(mx.utils.ObjectUtil.copy(myArrayCollection[i]));
}
return copyOfArrayCollection;
}
private function onButtonClick():void {
var copyOfArrayCollection:ArrayCollection = createCopyOfArrayCollection();
var sort:Sort = new Sort();
sort.compareFunction = sortFunction;
copyOfArrayCollection.sort = sort;
copyOfArrayCollection.refresh();
print_data_grid(copyOfArrayCollection);
}
private function sortFunction(a:Object, b:Object, array:Array = null):int {
//assuming all item_number contains one decimal
var itemNumberA:String = a.product.item_number;
var itemNumberASplitArray:Array = itemNumberA.split(".", 2);
var itemNumberB:String = b.product.item_number;
var itemNumberBSplitArray:Array = itemNumberB.split(".", 2);
if (Number(itemNumberASplitArray[0]) == Number(itemNumberBSplitArray[0])) {
if (Number(itemNumberASplitArray[1]) == Number(itemNumberBSplitArray[1])) {
return 0;
}
else if (Number(itemNumberASplitArray[1]) > Number(itemNumberBSplitArray[1])) {
return 1;
}
else {
return -1;
}
}
else if (Number(itemNumberASplitArray[0]) > Number(itemNumberBSplitArray[0])) {
return 1;
}
else {
return -1;
}
}
public function print_data_grid(array_collection):void {
my_data_grid2.dataProvider = array_collection
}
]]>
</fx:Script>
<mx:HBox verticalCenter="0" horizontalCenter="0">
<mx:Panel title="Unsorted Data">
<mx:DataGrid id="my_data_grid" dataProvider="{myArrayCollection}" height="400">
<mx:columns>
<mx:DataGridColumn headerText="# Item">
<mx:itemRenderer>
<fx:Component>
<mx:Label toolTip="{this.text}" text="{data.product.item_number}"/>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn headerText="Item Name">
<mx:itemRenderer>
<fx:Component>
<mx:Label toolTip="{this.text}" text="{data.product.name}"/>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
<mx:VBox height="100%">
<mx:Spacer percentHeight="50"/>
<mx:Button label=">>- Show sorted data ->>" click="{onButtonClick()}"/>
<mx:Spacer percentHeight="50"/>
</mx:VBox>
<mx:Panel title="Sorted Data">
<mx:DataGrid id="my_data_grid2" height="400">
<mx:columns>
<mx:DataGridColumn headerText="# Item">
<mx:itemRenderer>
<fx:Component>
<mx:Label toolTip="{this.text}" text="{data.product.item_number}"/>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn headerText="Item Name">
<mx:itemRenderer>
<fx:Component>
<mx:Label toolTip="{this.text}" text="{data.product.name}"/>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
</mx:HBox>
</s:Application>

Resources